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.5f, 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;
177 	int			damagePerCell;
178 	int			pa_te_type;
179 	int			power;
180 	int			power_used;
181 
182 	if (!damage)
183 		return 0;
184 
185 	index = 0;
186 
187 	client = ent->client;
188 
189 	if (dflags & DAMAGE_NO_ARMOR)
190 		return 0;
191 
192 	if (client)
193 	{
194 		power_armor_type = PowerArmorType (ent);
195 		if (power_armor_type != POWER_ARMOR_NONE)
196 		{
197 			index = ITEM_INDEX(FindItem("Cells"));
198 			power = client->pers.inventory[index];
199 		}
200 	}
201 	else if (ent->svflags & SVF_MONSTER)
202 	{
203 		power_armor_type = ent->monsterinfo.power_armor_type;
204 		power = ent->monsterinfo.power_armor_power;
205 	}
206 	else
207 		return 0;
208 
209 	if (power_armor_type == POWER_ARMOR_NONE)
210 		return 0;
211 
212 	if (!power)
213 		return 0;
214 
215 	if (power_armor_type == POWER_ARMOR_SCREEN)
216 	{
217 		vec3_t		vec;
218 		float		dot;
219 		vec3_t		forward;
220 
221 		// only works if damage point is in front
222 		AngleVectors (ent->s.angles, forward, NULL, NULL);
223 		VectorSubtract (point, ent->s.origin, vec);
224 		VectorNormalize (vec);
225 		dot = DotProduct (vec, forward);
226 		if (dot <= 0.3)
227 			return 0;
228 
229 		damagePerCell = 1;
230 		pa_te_type = TE_SCREEN_SPARKS;
231 		damage = damage / 3;
232 	}
233 	else
234 	{
235 		damagePerCell = 2;
236 		pa_te_type = TE_SHIELD_SPARKS;
237 		damage = (2 * damage) / 3;
238 	}
239 
240 	save = power * damagePerCell;
241 	if (!save)
242 		return 0;
243 	if (save > damage)
244 		save = damage;
245 
246 	SpawnDamage (pa_te_type, point, normal, save);
247 	ent->powerarmor_time = level.time + 0.2f;
248 
249 	power_used = save / damagePerCell;
250 
251 	if (client)
252 		client->pers.inventory[index] -= power_used;
253 	else
254 		ent->monsterinfo.power_armor_power -= power_used;
255 	return save;
256 }
257 
CheckArmor(edict_t * ent,vec3_t point,vec3_t normal,int damage,int te_sparks,int dflags)258 static int CheckArmor (edict_t *ent, vec3_t point, vec3_t normal, int damage, int te_sparks, int dflags)
259 {
260 	gclient_t	*client;
261 	int			save;
262 	int			index;
263 	gitem_t		*armor;
264 
265 	if (!damage)
266 		return 0;
267 
268 	client = ent->client;
269 
270 	if (!client)
271 		return 0;
272 
273 	if (dflags & DAMAGE_NO_ARMOR)
274 		return 0;
275 
276 	index = ArmorIndex (ent);
277 	if (!index)
278 		return 0;
279 
280 	armor = GetItemByIndex (index);
281 
282 	if (dflags & DAMAGE_ENERGY)
283 		save = (float)ceil(((gitem_armor_t *)armor->info)->energy_protection*damage);
284 	else
285 		save = (float)ceil(((gitem_armor_t *)armor->info)->normal_protection*damage);
286 	if (save >= client->pers.inventory[index])
287 		save = client->pers.inventory[index];
288 
289 	if (!save)
290 		return 0;
291 
292 	client->pers.inventory[index] -= save;
293 	SpawnDamage (te_sparks, point, normal, save);
294 
295 	return save;
296 }
297 
M_ReactToDamage(edict_t * targ,edict_t * attacker)298 void M_ReactToDamage (edict_t *targ, edict_t *attacker)
299 {
300 	if (!(attacker->client) && !(attacker->svflags & SVF_MONSTER))
301 		return;
302 
303 	if (attacker == targ || attacker == targ->enemy)
304 		return;
305 
306 	// if we are a good guy monster and our attacker is a player
307 	// or another good guy, do not get mad at them
308 	if (targ->monsterinfo.aiflags & AI_GOOD_GUY)
309 	{
310 		if (attacker->client || (attacker->monsterinfo.aiflags & AI_GOOD_GUY))
311 			return;
312 	}
313 
314 	// we now know that we are not both good guys
315 
316 	// if attacker is a client, get mad at them because he's good and we're not
317 	if (attacker->client)
318 	{
319 		targ->monsterinfo.aiflags &= ~AI_SOUND_TARGET;
320 
321 		// this can only happen in coop (both new and old enemies are clients)
322 		// only switch if can't see the current enemy
323 		if (targ->enemy && targ->enemy->client)
324 		{
325 			if (visible(targ, targ->enemy))
326 			{
327 				targ->oldenemy = attacker;
328 				return;
329 			}
330 			targ->oldenemy = targ->enemy;
331 		}
332 		targ->enemy = attacker;
333 		if (!(targ->monsterinfo.aiflags & AI_DUCKED))
334 			FoundTarget (targ);
335 		return;
336 	}
337 
338 	// it's the same base (walk/swim/fly) type and a different classname and it's not a tank
339 	// (they spray too much), get mad at them
340 	if (((targ->flags & (FL_FLY|FL_SWIM)) == (attacker->flags & (FL_FLY|FL_SWIM))) &&
341 		 (strcmp (targ->classname, attacker->classname) != 0) &&
342 		 (strcmp(attacker->classname, "monster_tank") != 0) &&
343 		 (strcmp(attacker->classname, "monster_supertank") != 0) &&
344 		 (strcmp(attacker->classname, "monster_makron") != 0) &&
345 		 (strcmp(attacker->classname, "monster_jorg") != 0) )
346 	{
347 		if (targ->enemy && targ->enemy->client)
348 			targ->oldenemy = targ->enemy;
349 		targ->enemy = attacker;
350 		if (!(targ->monsterinfo.aiflags & AI_DUCKED))
351 			FoundTarget (targ);
352 	}
353 	// if they *meant* to shoot us, then shoot back
354 	else if (attacker->enemy == targ)
355 	{
356 		if (targ->enemy && targ->enemy->client)
357 			targ->oldenemy = targ->enemy;
358 		targ->enemy = attacker;
359 		if (!(targ->monsterinfo.aiflags & AI_DUCKED))
360 			FoundTarget (targ);
361 	}
362 	// otherwise get mad at whoever they are mad at (help our buddy) unless it is us!
363 	else if (attacker->enemy && attacker->enemy != targ)
364 	{
365 		if (targ->enemy && targ->enemy->client)
366 			targ->oldenemy = targ->enemy;
367 		targ->enemy = attacker->enemy;
368 		if (!(targ->monsterinfo.aiflags & AI_DUCKED))
369 			FoundTarget (targ);
370 	}
371 }
372 
CheckTeamDamage(edict_t * targ,edict_t * attacker)373 qboolean CheckTeamDamage (edict_t *targ, edict_t *attacker)
374 {
375 		//FIXME make the next line real and uncomment this block
376 		// if ((ability to damage a teammate == OFF) && (targ's team == attacker's team))
377 	return false;
378 }
379 
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)380 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)
381 {
382 	gclient_t	*client;
383 	int			take;
384 	int			save;
385 	int			asave;
386 	int			psave;
387 	int			te_sparks;
388 
389 	if (!targ->takedamage)
390 		return;
391 
392 	// friendly fire avoidance
393 	// if enabled you can't hurt teammates (but you can hurt yourself)
394 	// knockback still occurs
395 	if ((targ != attacker) && ((deathmatch->value && ((int)(dmflags->value) & (DF_MODELTEAMS | DF_SKINTEAMS))) || coop->value))
396 	{
397 		if (OnSameTeam (targ, attacker))
398 		{
399 			if ((int)(dmflags->value) & DF_NO_FRIENDLY_FIRE)
400 				damage = 0;
401 			else
402 				mod |= MOD_FRIENDLY_FIRE;
403 		}
404 	}
405 	meansOfDeath = mod;
406 
407 	// easy mode takes half damage
408 	if (skill->value == 0 && deathmatch->value == 0 && targ->client)
409 	{
410 		damage *= 0.5f;
411 		if (!damage)
412 			damage = 1;
413 	}
414 
415 	client = targ->client;
416 
417 	if (dflags & DAMAGE_BULLET)
418 		te_sparks = TE_BULLET_SPARKS;
419 	else
420 		te_sparks = TE_SPARKS;
421 
422 	VectorNormalize(dir);
423 
424 // bonus damage for suprising a monster
425 	if (!(dflags & DAMAGE_RADIUS) && (targ->svflags & SVF_MONSTER) && (attacker->client) && (!targ->enemy) && (targ->health > 0))
426 		damage *= 2;
427 
428 	if (targ->flags & FL_NO_KNOCKBACK)
429 		knockback = 0;
430 
431 // figure momentum add
432 	if (!(dflags & DAMAGE_NO_KNOCKBACK))
433 	{
434 		if ((knockback) && (targ->movetype != MOVETYPE_NONE) && (targ->movetype != MOVETYPE_BOUNCE) && (targ->movetype != MOVETYPE_PUSH) && (targ->movetype != MOVETYPE_STOP))
435 		{
436 			vec3_t	kvel;
437 			float	mass;
438 
439 			if (targ->mass < 50)
440 				mass = 50;
441 			else
442 				mass = targ->mass;
443 
444 			if (targ->client  && attacker == targ)
445 				VectorScale (dir, 1600.0f * (float)knockback / mass, kvel);	// the rocket jump hack...
446 			else
447 				VectorScale (dir, 500.0f * (float)knockback / mass, kvel);
448 
449 			VectorAdd (targ->velocity, kvel, targ->velocity);
450 		}
451 	}
452 
453 	take = damage;
454 	save = 0;
455 
456 	// check for godmode
457 	if ( (targ->flags & FL_GODMODE) && !(dflags & DAMAGE_NO_PROTECTION) )
458 	{
459 		take = 0;
460 		save = damage;
461 		SpawnDamage (te_sparks, point, normal, save);
462 	}
463 
464 	// check for invincibility
465 	if ((client && client->invincible_framenum > level.framenum ) && !(dflags & DAMAGE_NO_PROTECTION))
466 	{
467 		if (targ->pain_debounce_time < level.time)
468 		{
469 			gi.sound(targ, CHAN_ITEM, gi.soundindex("items/protect4.wav"), 1, ATTN_NORM, 0);
470 			targ->pain_debounce_time = level.time + 2;
471 		}
472 		take = 0;
473 		save = damage;
474 	}
475 
476 	psave = CheckPowerArmor (targ, point, normal, take, dflags);
477 	take -= psave;
478 
479 	asave = CheckArmor (targ, point, normal, take, te_sparks, dflags);
480 	take -= asave;
481 
482 	//treat cheat/powerup savings the same as armor
483 	asave += save;
484 
485 	// team damage avoidance
486 	if (!(dflags & DAMAGE_NO_PROTECTION) && CheckTeamDamage (targ, attacker))
487 		return;
488 
489 // do the damage
490 	if (take)
491 	{
492 		if ((targ->svflags & SVF_MONSTER) || (client))
493 			SpawnDamage (TE_BLOOD, point, normal, take);
494 		else
495 			SpawnDamage (te_sparks, point, normal, take);
496 
497 
498 		targ->health = targ->health - take;
499 
500 		if (targ->health <= 0)
501 		{
502 			if ((targ->svflags & SVF_MONSTER) || (client))
503 				targ->flags |= FL_NO_KNOCKBACK;
504 			Killed (targ, inflictor, attacker, take, point);
505 			return;
506 		}
507 	}
508 
509 	if (targ->svflags & SVF_MONSTER)
510 	{
511 		M_ReactToDamage (targ, attacker);
512 		if (!(targ->monsterinfo.aiflags & AI_DUCKED) && (take))
513 		{
514 			targ->pain (targ, attacker, knockback, take);
515 			// nightmare mode monsters don't go into pain frames often
516 			if (skill->value == 3)
517 				targ->pain_debounce_time = level.time + 5;
518 		}
519 	}
520 	else if (client)
521 	{
522 		if (!(targ->flags & FL_GODMODE) && (take))
523 			targ->pain (targ, attacker, knockback, take);
524 	}
525 	else if (take)
526 	{
527 		if (targ->pain)
528 			targ->pain (targ, attacker, knockback, take);
529 	}
530 
531 	// add to the damage inflicted on a player this frame
532 	// the total will be turned into screen blends and view angle kicks
533 	// at the end of the frame
534 	if (client)
535 	{
536 		client->damage_parmor += psave;
537 		client->damage_armor += asave;
538 		client->damage_blood += take;
539 		client->damage_knockback += knockback;
540 		VectorCopy (point, client->damage_from);
541 	}
542 }
543 
544 
545 /*
546 ============
547 T_RadiusDamage
548 ============
549 */
T_RadiusDamage(edict_t * inflictor,edict_t * attacker,float damage,edict_t * ignore,float radius,int mod)550 void T_RadiusDamage (edict_t *inflictor, edict_t *attacker, float damage, edict_t *ignore, float radius, int mod)
551 {
552 	float	points;
553 	edict_t	*ent = NULL;
554 	vec3_t	v;
555 	vec3_t	dir;
556 
557 	while ((ent = findradius(ent, inflictor->s.origin, radius)) != NULL)
558 	{
559 		if (ent == ignore)
560 			continue;
561 		if (!ent->takedamage)
562 			continue;
563 
564 		VectorAdd (ent->mins, ent->maxs, v);
565 		VectorMA (ent->s.origin, 0.5f, v, v);
566 		VectorSubtract (inflictor->s.origin, v, v);
567 		points = damage - 0.5f * VectorLength (v);
568 		if (ent == attacker)
569 			points = points * 0.5f;
570 		if (points > 0)
571 		{
572 			if (CanDamage (ent, inflictor))
573 			{
574 				VectorSubtract (ent->s.origin, inflictor->s.origin, dir);
575 				T_Damage (ent, inflictor, attacker, dir, inflictor->s.origin, vec3_origin, (int)points, (int)points, DAMAGE_RADIUS, mod);
576 			}
577 		}
578 	}
579 }
580