Quake-2

Quake 2 GPL Source Release
Log | Files | Refs

g_combat.c (14858B)


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