Quake-2

Quake 2 GPL Source Release
Log | Files | Refs

g_combat.c (14553B)


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