1 /*
2  * Copyright (C) 1997-2001 Id Software, Inc.
3  *
4  * This program is free software; you can redistribute it and/or modify it under
5  * the terms of the GNU General Public License as published by the Free
6  * Software Foundation; either version 2 of the License, or (at your option)
7  * any later version.
8  *
9  * This program is distributed in the hope that it will be useful, but WITHOUT
10  * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
11  * 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 along with
16  * this program; if not, write to the Free Software Foundation, Inc., 59
17  * Temple Place - Suite 330, Boston, MA  02111-1307, USA.
18  *
19  */
20 /* g_combat.c */
21 
22 #include "g_local.h"
23 
24 /*
25  * ============ CanDamage
26  *
27  * Returns true if the inflictor can directly damage the target.  Used for
28  * explosions and melee attacks. ============
29  */
30 qboolean
CanDamage(edict_t * targ,edict_t * inflictor)31 CanDamage(edict_t * targ, edict_t * inflictor)
32 {
33 	vec3_t		dest;
34 	trace_t		trace;
35 
36 	/* bmodels need special checking because their origin is 0,0,0 */
37 	if (targ->movetype == MOVETYPE_PUSH) {
38 		VectorAdd(targ->absmin, targ->absmax, dest);
39 		VectorScale(dest, 0.5, dest);
40 		trace = gi.trace(inflictor->s.origin, vec3_origin, vec3_origin, dest, inflictor, MASK_SOLID);
41 		if (trace.fraction == 1.0)
42 			return true;
43 		if (trace.ent == targ)
44 			return true;
45 		return false;
46 	}
47 	trace = gi.trace(inflictor->s.origin, vec3_origin, vec3_origin, targ->s.origin, inflictor, MASK_SOLID);
48 	if (trace.fraction == 1.0)
49 		return true;
50 
51 	VectorCopy(targ->s.origin, dest);
52 	dest[0] += 15.0;
53 	dest[1] += 15.0;
54 	trace = gi.trace(inflictor->s.origin, vec3_origin, vec3_origin, dest, 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 
80 	return false;
81 }
82 
83 
84 /*
85  * ============ Killed ============
86  */
87 void
Killed(edict_t * targ,edict_t * inflictor,edict_t * attacker,int damage,vec3_t point)88 Killed(edict_t * targ, edict_t * inflictor, edict_t * attacker, int damage, vec3_t point)
89 {
90 	if (targ->health < -999)
91 		targ->health = -999;
92 
93 	targ->enemy = attacker;
94 
95 	if ((targ->svflags & SVF_MONSTER) && (targ->deadflag != DEAD_DEAD)) {
96 
97 		/*
98 		 * targ->svflags |= SVF_DEADMONSTER;	// now treat as a
99 		 * different content type
100 		 */
101 		if (!(targ->monsterinfo.aiflags & AI_GOOD_GUY)) {
102 			level.killed_monsters++;
103 			if (coop->value && attacker->client)
104 				attacker->client->resp.score++;
105 
106 			/*
107 			 * medics won't heal monsters that they kill
108 			 * themselves
109 			 */
110 			if (strcmp(attacker->classname, "monster_medic") == 0)
111 				targ->owner = attacker;
112 		}
113 	}
114 	if (targ->movetype == MOVETYPE_PUSH || targ->movetype == MOVETYPE_STOP || targ->movetype == MOVETYPE_NONE) {	/* doors, triggers, etc */
115 		targ->die(targ, inflictor, attacker, damage, point);
116 		return;
117 	}
118 	if ((targ->svflags & SVF_MONSTER) && (targ->deadflag != DEAD_DEAD)) {
119 		targ->touch = NULL;
120 		monster_death_use(targ);
121 	}
122 	targ->die(targ, inflictor, attacker, damage, point);
123 }
124 
125 
126 /*
127  * ================ SpawnDamage ================
128  */
129 void
SpawnDamage(int type,vec3_t origin,vec3_t normal,int damage)130 SpawnDamage(int type, vec3_t origin, vec3_t normal, int damage)
131 {
132 	if (damage > 255)
133 		damage = 255;
134 	gi.WriteByte(svc_temp_entity);
135 	gi.WriteByte(type);
136 	/* gi.WriteByte (damage); */
137 	gi.WritePosition(origin);
138 	gi.WriteDir(normal);
139 	gi.multicast(origin, MULTICAST_PVS);
140 }
141 
142 
143 /*
144  * ============ T_Damage
145  *
146  * targ		entity that is being damaged inflictor	entity that is
147  * causing the damage attacker	entity that caused the inflictor to damage
148  * targ example: targ=monster, inflictor=rocket, attacker=player
149  *
150  * dir			direction of the attack point		point at
151  * which the damage is being inflicted normal		normal vector from
152  * that point damage		amount of damage being inflicted knockback
153  * force to be applied against targ as a result of the damage
154  *
155  * dflags		these flags are used to control how T_Damage works
156  * DAMAGE_RADIUS			damage was indirect (from a nearby
157  * explosion) DAMAGE_NO_ARMOR			armor does not protect from
158  * this damage DAMAGE_ENERGY			damage is from an energy
159  * based weapon DAMAGE_NO_KNOCKBACK		do not affect velocity, just
160  * view angles DAMAGE_BULLET			damage is from a bullet (used
161  * for ricochets) DAMAGE_NO_PROTECTION	kills godmode, armor, everything
162  * ============
163  */
164 static int
CheckPowerArmor(edict_t * ent,vec3_t point,vec3_t normal,int damage,int dflags)165 CheckPowerArmor(edict_t * ent, vec3_t point, vec3_t normal, int damage, int dflags)
166 {
167 	gclient_t      *client;
168 	int		save;
169 	int		power_armor_type;
170 	int		index;
171 	int		damagePerCell;
172 	int		pa_te_type;
173 	int		power;
174 	int		power_used;
175 
176 	if (!damage)
177 		return 0;
178 
179 	client = ent->client;
180 
181 	if (dflags & DAMAGE_NO_ARMOR)
182 		return 0;
183 
184 	if (client) {
185 		power_armor_type = PowerArmorType(ent);
186 		if (power_armor_type != POWER_ARMOR_NONE) {
187 			index = ITEM_INDEX(FindItem("Cells"));
188 			power = client->pers.inventory[index];
189 		}
190 	} else if (ent->svflags & SVF_MONSTER) {
191 		power_armor_type = ent->monsterinfo.power_armor_type;
192 		power = ent->monsterinfo.power_armor_power;
193 		index = 0;
194 	} else
195 		return 0;
196 
197 	if (power_armor_type == POWER_ARMOR_NONE)
198 		return 0;
199 	if (!power)
200 		return 0;
201 
202 	if (power_armor_type == POWER_ARMOR_SCREEN) {
203 		vec3_t		vec;
204 		float		dot;
205 		vec3_t		forward;
206 
207 		/* only works if damage point is in front */
208 		AngleVectors(ent->s.angles, forward, NULL, NULL);
209 		VectorSubtract(point, ent->s.origin, vec);
210 		VectorNormalize(vec);
211 		dot = DotProduct(vec, forward);
212 		if (dot <= 0.3)
213 			return 0;
214 
215 		damagePerCell = 1;
216 		pa_te_type = TE_SCREEN_SPARKS;
217 		damage = damage / 3;
218 	} else {
219 		damagePerCell = 2;
220 		pa_te_type = TE_SHIELD_SPARKS;
221 		damage = (2 * damage) / 3;
222 	}
223 
224 	save = power * damagePerCell;
225 	if (!save)
226 		return 0;
227 	if (save > damage)
228 		save = damage;
229 
230 	SpawnDamage(pa_te_type, point, normal, save);
231 	ent->powerarmor_time = level.time + 0.2;
232 
233 	power_used = save / damagePerCell;
234 
235 	if (client)
236 		client->pers.inventory[index] -= power_used;
237 	else
238 		ent->monsterinfo.power_armor_power -= power_used;
239 	return save;
240 }
241 
242 static int
CheckArmor(edict_t * ent,vec3_t point,vec3_t normal,int damage,int te_sparks,int dflags)243 CheckArmor(edict_t * ent, vec3_t point, vec3_t normal, int damage, int te_sparks, int dflags)
244 {
245 	gclient_t      *client;
246 	int		save;
247 	int		index;
248 	gitem_t        *armor;
249 
250 	if (!damage)
251 		return 0;
252 
253 	client = ent->client;
254 
255 	if (!client)
256 		return 0;
257 
258 	if (dflags & DAMAGE_NO_ARMOR)
259 		return 0;
260 
261 	index = ArmorIndex(ent);
262 	if (!index)
263 		return 0;
264 
265 	armor = GetItemByIndex(index);
266 
267 	if (dflags & DAMAGE_ENERGY)
268 		save = ceil(((gitem_armor_t *) armor->info)->energy_protection * damage);
269 	else
270 		save = ceil(((gitem_armor_t *) armor->info)->normal_protection * damage);
271 	if (save >= client->pers.inventory[index])
272 		save = client->pers.inventory[index];
273 
274 	if (!save)
275 		return 0;
276 
277 	client->pers.inventory[index] -= save;
278 	SpawnDamage(te_sparks, point, normal, save);
279 
280 	return save;
281 }
282 
283 void
M_ReactToDamage(edict_t * targ,edict_t * attacker)284 M_ReactToDamage(edict_t * targ, edict_t * attacker)
285 {
286 	if (!(attacker->client) && !(attacker->svflags & SVF_MONSTER))
287 		return;
288 
289 	if (attacker == targ || attacker == targ->enemy)
290 		return;
291 
292 	/* if we are a good guy monster and our attacker is a player */
293 	/* or another good guy, do not get mad at them */
294 	if (targ->monsterinfo.aiflags & AI_GOOD_GUY) {
295 		if (attacker->client || (attacker->monsterinfo.aiflags & AI_GOOD_GUY))
296 			return;
297 	}
298 	/* we now know that we are not both good guys */
299 
300 	/*
301 	 * if attacker is a client, get mad at them because he's good and
302 	 * we're not
303 	 */
304 	if (attacker->client) {
305 		targ->monsterinfo.aiflags &= ~AI_SOUND_TARGET;
306 
307 		/*
308 		 * this can only happen in coop (both new and old enemies are
309 		 * clients)
310 		 */
311 		/* only switch if can't see the current enemy */
312 		if (targ->enemy && targ->enemy->client) {
313 			if (visible(targ, targ->enemy)) {
314 				targ->oldenemy = attacker;
315 				return;
316 			}
317 			targ->oldenemy = targ->enemy;
318 		}
319 		targ->enemy = attacker;
320 		if (!(targ->monsterinfo.aiflags & AI_DUCKED))
321 			FoundTarget(targ);
322 		return;
323 	}
324 
325 	/*
326 	 * it's the same base (walk/swim/fly) type and a different classname
327 	 * and it's not a tank
328 	 */
329 	/* (they spray too much), get mad at them */
330 	if (((targ->flags & (FL_FLY | FL_SWIM)) == (attacker->flags & (FL_FLY | FL_SWIM))) &&
331 	    (strcmp(targ->classname, attacker->classname) != 0) &&
332 	    (strcmp(attacker->classname, "monster_tank") != 0) &&
333 	    (strcmp(attacker->classname, "monster_supertank") != 0) &&
334 	    (strcmp(attacker->classname, "monster_makron") != 0) &&
335 	    (strcmp(attacker->classname, "monster_jorg") != 0)) {
336 		if (targ->enemy && targ->enemy->client)
337 			targ->oldenemy = targ->enemy;
338 		targ->enemy = attacker;
339 		if (!(targ->monsterinfo.aiflags & AI_DUCKED))
340 			FoundTarget(targ);
341 	}
342 	/* if they *meant* to shoot us, then shoot back */
343 	else if (attacker->enemy == targ) {
344 		if (targ->enemy && targ->enemy->client)
345 			targ->oldenemy = targ->enemy;
346 		targ->enemy = attacker;
347 		if (!(targ->monsterinfo.aiflags & AI_DUCKED))
348 			FoundTarget(targ);
349 	}
350 
351 	/*
352 	 * otherwise get mad at whoever they are mad at (help our buddy)
353 	 * unless it is us!
354 	 */
355 	else if (attacker->enemy && attacker->enemy != targ) {
356 		if (targ->enemy && targ->enemy->client)
357 			targ->oldenemy = targ->enemy;
358 		targ->enemy = attacker->enemy;
359 		if (!(targ->monsterinfo.aiflags & AI_DUCKED))
360 			FoundTarget(targ);
361 	}
362 }
363 
364 qboolean
CheckTeamDamage(edict_t * targ,edict_t * attacker)365 CheckTeamDamage(edict_t * targ, edict_t * attacker)
366 {
367 	/* FIXME make the next line real and uncomment this block */
368 
369 	/*
370 	 * if ((ability to damage a teammate == OFF) && (targ's team ==
371 	 * attacker's team))
372 	 */
373 	return false;
374 }
375 
376 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)377 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)
378 {
379 	gclient_t      *client;
380 	int		take;
381 	int		save;
382 	int		asave;
383 	int		psave;
384 	int		te_sparks;
385 
386 	if (!targ->takedamage)
387 		return;
388 
389 	/* friendly fire avoidance */
390 	/* if enabled you can't hurt teammates (but you can hurt yourself) */
391 	/* knockback still occurs */
392 	if ((targ != attacker) && ((deathmatch->value && ((int)(dmflags->value) & (DF_MODELTEAMS | DF_SKINTEAMS))) || coop->value)) {
393 		if (OnSameTeam(targ, attacker)) {
394 			if ((int)(dmflags->value) & DF_NO_FRIENDLY_FIRE)
395 				damage = 0;
396 			else
397 				mod |= MOD_FRIENDLY_FIRE;
398 		}
399 	}
400 	meansOfDeath = mod;
401 
402 	/* easy mode takes half damage */
403 	if (skill->value == 0 && deathmatch->value == 0 && targ->client) {
404 		damage *= 0.5;
405 		if (!damage)
406 			damage = 1;
407 	}
408 	client = targ->client;
409 
410 	if (dflags & DAMAGE_BULLET)
411 		te_sparks = TE_BULLET_SPARKS;
412 	else
413 		te_sparks = TE_SPARKS;
414 
415 	VectorNormalize(dir);
416 
417 	/* bonus damage for suprising a monster */
418 	if (!(dflags & DAMAGE_RADIUS) && (targ->svflags & SVF_MONSTER) && (attacker->client) && (!targ->enemy) && (targ->health > 0))
419 		damage *= 2;
420 
421 	if (targ->flags & FL_NO_KNOCKBACK)
422 		knockback = 0;
423 
424 	/* figure momentum add */
425 	if (!(dflags & DAMAGE_NO_KNOCKBACK)) {
426 		if ((knockback) && (targ->movetype != MOVETYPE_NONE) && (targ->movetype != MOVETYPE_BOUNCE) && (targ->movetype != MOVETYPE_PUSH) && (targ->movetype != MOVETYPE_STOP)) {
427 			vec3_t		kvel;
428 			float		mass;
429 
430 			if (targ->mass < 50)
431 				mass = 50;
432 			else
433 				mass = targ->mass;
434 
435 			if (targ->client && attacker == targ)
436 				VectorScale(dir, 1600.0 * (float)knockback / mass, kvel);	/* the rocket jump
437 												 * hack... */
438 			else
439 				VectorScale(dir, 500.0 * (float)knockback / mass, kvel);
440 
441 			VectorAdd(targ->velocity, kvel, targ->velocity);
442 		}
443 	}
444 	take = damage;
445 	save = 0;
446 
447 	/* check for godmode */
448 	if ((targ->flags & FL_GODMODE) && !(dflags & DAMAGE_NO_PROTECTION)) {
449 		take = 0;
450 		save = damage;
451 		SpawnDamage(te_sparks, point, normal, save);
452 	}
453 	/* check for invincibility */
454 	if ((client && client->invincible_framenum > level.framenum) && !(dflags & DAMAGE_NO_PROTECTION)) {
455 		if (targ->pain_debounce_time < level.time) {
456 			gi.sound(targ, CHAN_ITEM, gi.soundindex("items/protect4.wav"), 1, ATTN_NORM, 0);
457 			targ->pain_debounce_time = level.time + 2;
458 		}
459 		take = 0;
460 		save = damage;
461 	}
462 	psave = CheckPowerArmor(targ, point, normal, take, dflags);
463 	take -= psave;
464 
465 	asave = CheckArmor(targ, point, normal, take, te_sparks, dflags);
466 	take -= asave;
467 
468 	/* treat cheat/powerup savings the same as armor */
469 	asave += save;
470 
471 	/* team damage avoidance */
472 	if (!(dflags & DAMAGE_NO_PROTECTION) && CheckTeamDamage(targ, attacker))
473 		return;
474 
475 	/* do the damage */
476 	if (take) {
477 		if ((targ->svflags & SVF_MONSTER) || (client))
478 			SpawnDamage(TE_BLOOD, point, normal, take);
479 		else
480 			SpawnDamage(te_sparks, point, normal, take);
481 
482 
483 		targ->health = targ->health - take;
484 
485 		if (targ->health <= 0) {
486 			if ((targ->svflags & SVF_MONSTER) || (client))
487 				targ->flags |= FL_NO_KNOCKBACK;
488 			Killed(targ, inflictor, attacker, take, point);
489 			return;
490 		}
491 	}
492 	if (targ->svflags & SVF_MONSTER) {
493 		M_ReactToDamage(targ, attacker);
494 		if (!(targ->monsterinfo.aiflags & AI_DUCKED) && (take)) {
495 			targ->pain(targ, attacker, knockback, take);
496 
497 			/*
498 			 * nightmare mode monsters don't go into pain frames
499 			 * often
500 			 */
501 			if (skill->value == 3)
502 				targ->pain_debounce_time = level.time + 5;
503 		}
504 	} else if (client) {
505 		if (!(targ->flags & FL_GODMODE) && (take))
506 			targ->pain(targ, attacker, knockback, take);
507 	} else if (take) {
508 		if (targ->pain)
509 			targ->pain(targ, attacker, knockback, take);
510 	}
511 	/* add to the damage inflicted on a player this frame */
512 	/* the total will be turned into screen blends and view angle kicks */
513 	/* at the end of the frame */
514 	if (client) {
515 		client->damage_parmor += psave;
516 		client->damage_armor += asave;
517 		client->damage_blood += take;
518 		client->damage_knockback += knockback;
519 		VectorCopy(point, client->damage_from);
520 	}
521 }
522 
523 
524 /*
525  * ============ T_RadiusDamage ============
526  */
527 void
T_RadiusDamage(edict_t * inflictor,edict_t * attacker,float damage,edict_t * ignore,float radius,int mod)528 T_RadiusDamage(edict_t * inflictor, edict_t * attacker, float damage, edict_t * ignore, float radius, int mod)
529 {
530 	float		points;
531 	edict_t        *ent = NULL;
532 	vec3_t		v;
533 	vec3_t		dir;
534 
535 	while ((ent = findradius(ent, inflictor->s.origin, radius)) != NULL) {
536 		if (ent == ignore)
537 			continue;
538 		if (!ent->takedamage)
539 			continue;
540 
541 		VectorAdd(ent->mins, ent->maxs, v);
542 		VectorMA(ent->s.origin, 0.5, v, v);
543 		VectorSubtract(inflictor->s.origin, v, v);
544 		points = damage - 0.5 * VectorLength(v);
545 		if (ent == attacker)
546 			points = points * 0.5;
547 		if (points > 0) {
548 			if (CanDamage(ent, inflictor)) {
549 				VectorSubtract(ent->s.origin, inflictor->s.origin, dir);
550 				T_Damage(ent, inflictor, attacker, dir, inflictor->s.origin, vec3_origin, (int)points, (int)points, DAMAGE_RADIUS, mod);
551 			}
552 		}
553 	}
554 }
555