File: game\g_combat.c

    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 }
  577