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