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