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 = 1; // power armor is weaker in CTF
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 		// this can only happen in coop (both new and old enemies are clients)
318 		// only switch if can't see the current enemy
319 		if (targ->enemy && targ->enemy->client)
320 		{
321 			if (visible(targ, targ->enemy))
322 			{
323 				targ->oldenemy = attacker;
324 				return;
325 			}
326 			targ->oldenemy = targ->enemy;
327 		}
328 		targ->enemy = attacker;
329 		if (!(targ->monsterinfo.aiflags & AI_DUCKED))
330 			FoundTarget (targ);
331 		return;
332 	}
333 
334 	// it's the same base (walk/swim/fly) type and a different classname and it's not a tank
335 	// (they spray too much), get mad at them
336 	if (((targ->flags & (FL_FLY|FL_SWIM)) == (attacker->flags & (FL_FLY|FL_SWIM))) &&
337 		 (strcmp (targ->classname, attacker->classname) != 0) &&
338 		 (strcmp(attacker->classname, "monster_tank") != 0) &&
339 		 (strcmp(attacker->classname, "monster_supertank") != 0) &&
340 		 (strcmp(attacker->classname, "monster_makron") != 0) &&
341 		 (strcmp(attacker->classname, "monster_jorg") != 0) )
342 	{
343 		if (targ->enemy)
344 			if (targ->enemy->client)
345 				targ->oldenemy = targ->enemy;
346 		targ->enemy = attacker;
347 		if (!(targ->monsterinfo.aiflags & AI_DUCKED))
348 			FoundTarget (targ);
349 	}
350 	else
351 	// otherwise get mad at whoever they are mad at (help our buddy)
352 	{
353 		if (targ->enemy)
354 			if (targ->enemy->client)
355 				targ->oldenemy = targ->enemy;
356 		targ->enemy = attacker->enemy;
357 		FoundTarget (targ);
358 	}
359 }
360 
CheckTeamDamage(edict_t * targ,edict_t * attacker)361 qboolean CheckTeamDamage (edict_t *targ, edict_t *attacker)
362 {
363 //ZOID
364 	if (ctf->value && targ->client && attacker->client)
365 		if (targ->client->resp.ctf_team == attacker->client->resp.ctf_team &&
366 			targ != attacker)
367 			return true;
368 //ZOID
369 
370 		//FIXME make the next line real and uncomment this block
371 		// if ((ability to damage a teammate == OFF) && (targ's team == attacker's team))
372 	return false;
373 }
374 
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)375 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)
376 {
377 	gclient_t	*client;
378 	int			take;
379 	int			save;
380 	int			asave;
381 	int			psave;
382 	int			te_sparks;
383 
384 	if (!targ->takedamage)
385 		return;
386 
387 	// friendly fire avoidance
388 	// if enabled you can't hurt teammates (but you can hurt yourself)
389 	// knockback still occurs
390 	if ((targ != attacker) && ((deathmatch->value && ((int)(dmflags->value) & (DF_MODELTEAMS | DF_SKINTEAMS))) || coop->value))
391 	{
392 		if (OnSameTeam (targ, attacker))
393 		{
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 	{
405 		damage *= 0.5;
406 		if (!damage)
407 			damage = 1;
408 	}
409 
410 	client = targ->client;
411 
412 	if (dflags & DAMAGE_BULLET)
413 		te_sparks = TE_BULLET_SPARKS;
414 	else
415 		te_sparks = TE_SPARKS;
416 
417 	VectorNormalize(dir);
418 
419 // bonus damage for suprising a monster
420 	if (!(dflags & DAMAGE_RADIUS) && (targ->svflags & SVF_MONSTER) && (attacker->client) && (!targ->enemy) && (targ->health > 0))
421 		damage *= 2;
422 
423 //ZOID
424 //strength tech
425 	damage = CTFApplyStrength(attacker, damage);
426 //ZOID
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.0 * (float)knockback / mass, kvel);	// the rocket jump hack...
446 			else
447 				VectorScale (dir, 500.0 * (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 //ZOID
477 //team armor protect
478 	if (ctf->value && targ->client && attacker->client &&
479 		targ->client->resp.ctf_team == attacker->client->resp.ctf_team &&
480 		targ != attacker && ((int)dmflags->value & DF_ARMOR_PROTECT)) {
481 		psave = asave = 0;
482 	} else {
483 //ZOID
484 		psave = CheckPowerArmor (targ, point, normal, take, dflags);
485 		take -= psave;
486 
487 		asave = CheckArmor (targ, point, normal, take, te_sparks, dflags);
488 		take -= asave;
489 	}
490 
491 	//treat cheat/powerup savings the same as armor
492 	asave += save;
493 
494 //ZOID
495 //resistance tech
496 	take = CTFApplyResistance(targ, take);
497 //ZOID
498 
499 	// team damage avoidance
500 	if (!(dflags & DAMAGE_NO_PROTECTION) && CheckTeamDamage (targ, attacker))
501 		return;
502 
503 //ZOID
504 	CTFCheckHurtCarrier(targ, attacker);
505 //ZOID
506 
507 // do the damage
508 	if (take)
509 	{
510 		if ((targ->svflags & SVF_MONSTER) || (client))
511 			SpawnDamage (TE_BLOOD, point, normal, take);
512 		else
513 			SpawnDamage (te_sparks, point, normal, take);
514 
515 		if (!CTFMatchSetup())
516 			targ->health = targ->health - take;
517 
518 		if (targ->health <= 0)
519 		{
520 			if ((targ->svflags & SVF_MONSTER) || (client))
521 				targ->flags |= FL_NO_KNOCKBACK;
522 			Killed (targ, inflictor, attacker, take, point);
523 			return;
524 		}
525 	}
526 
527 	if (targ->svflags & SVF_MONSTER)
528 	{
529 		M_ReactToDamage (targ, attacker);
530 		if (!(targ->monsterinfo.aiflags & AI_DUCKED) && (take))
531 		{
532 			targ->pain (targ, attacker, knockback, take);
533 			// nightmare mode monsters don't go into pain frames often
534 			if (skill->value == 3)
535 				targ->pain_debounce_time = level.time + 5;
536 		}
537 	}
538 	else if (client)
539 	{
540 		if (!(targ->flags & FL_GODMODE) && (take) && !CTFMatchSetup())
541 			targ->pain (targ, attacker, knockback, take);
542 	}
543 	else if (take)
544 	{
545 		if (targ->pain)
546 			targ->pain (targ, attacker, knockback, take);
547 	}
548 
549 	// add to the damage inflicted on a player this frame
550 	// the total will be turned into screen blends and view angle kicks
551 	// at the end of the frame
552 	if (client)
553 	{
554 		client->damage_parmor += psave;
555 		client->damage_armor += asave;
556 		client->damage_blood += take;
557 		client->damage_knockback += knockback;
558 		VectorCopy (point, client->damage_from);
559 	}
560 }
561 
562 
563 /*
564 ============
565 T_RadiusDamage
566 ============
567 */
T_RadiusDamage(edict_t * inflictor,edict_t * attacker,float damage,edict_t * ignore,float radius,int mod)568 void T_RadiusDamage (edict_t *inflictor, edict_t *attacker, float damage, edict_t *ignore, float radius, int mod)
569 {
570 	float	points;
571 	edict_t	*ent = NULL;
572 	vec3_t	v;
573 	vec3_t	dir;
574 
575 	while ((ent = findradius(ent, inflictor->s.origin, radius)) != NULL)
576 	{
577 		if (ent == ignore)
578 			continue;
579 		if (!ent->takedamage)
580 			continue;
581 
582 		VectorAdd (ent->mins, ent->maxs, v);
583 		VectorMA (ent->s.origin, 0.5, v, v);
584 		VectorSubtract (inflictor->s.origin, v, v);
585 		points = damage - 0.5 * VectorLength (v);
586 		if (ent == attacker)
587 			points = points * 0.5;
588 		if (points > 0)
589 		{
590 			if (CanDamage (ent, inflictor))
591 			{
592 				VectorSubtract (ent->s.origin, inflictor->s.origin, dir);
593 				T_Damage (ent, inflictor, attacker, dir, inflictor->s.origin, vec3_origin, (int)points, (int)points, DAMAGE_RADIUS, mod);
594 			}
595 		}
596 	}
597 }
598