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 }