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