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.5f, 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 index = 0;
186
187 client = ent->client;
188
189 if (dflags & DAMAGE_NO_ARMOR)
190 return 0;
191
192 if (client)
193 {
194 power_armor_type = PowerArmorType (ent);
195 if (power_armor_type != POWER_ARMOR_NONE)
196 {
197 index = ITEM_INDEX(FindItem("Cells"));
198 power = client->pers.inventory[index];
199 }
200 }
201 else if (ent->svflags & SVF_MONSTER)
202 {
203 power_armor_type = ent->monsterinfo.power_armor_type;
204 power = ent->monsterinfo.power_armor_power;
205 }
206 else
207 return 0;
208
209 if (power_armor_type == POWER_ARMOR_NONE)
210 return 0;
211
212 if (!power)
213 return 0;
214
215 if (power_armor_type == POWER_ARMOR_SCREEN)
216 {
217 vec3_t vec;
218 float dot;
219 vec3_t forward;
220
221 // only works if damage point is in front
222 AngleVectors (ent->s.angles, forward, NULL, NULL);
223 VectorSubtract (point, ent->s.origin, vec);
224 VectorNormalize (vec);
225 dot = DotProduct (vec, forward);
226 if (dot <= 0.3)
227 return 0;
228
229 damagePerCell = 1;
230 pa_te_type = TE_SCREEN_SPARKS;
231 damage = damage / 3;
232 }
233 else
234 {
235 damagePerCell = 2;
236 pa_te_type = TE_SHIELD_SPARKS;
237 damage = (2 * damage) / 3;
238 }
239
240 save = power * damagePerCell;
241 if (!save)
242 return 0;
243 if (save > damage)
244 save = damage;
245
246 SpawnDamage (pa_te_type, point, normal, save);
247 ent->powerarmor_time = level.time + 0.2f;
248
249 power_used = save / damagePerCell;
250
251 if (client)
252 client->pers.inventory[index] -= power_used;
253 else
254 ent->monsterinfo.power_armor_power -= power_used;
255 return save;
256 }
257
CheckArmor(edict_t * ent,vec3_t point,vec3_t normal,int damage,int te_sparks,int dflags)258 static int CheckArmor (edict_t *ent, vec3_t point, vec3_t normal, int damage, int te_sparks, int dflags)
259 {
260 gclient_t *client;
261 int save;
262 int index;
263 gitem_t *armor;
264
265 if (!damage)
266 return 0;
267
268 client = ent->client;
269
270 if (!client)
271 return 0;
272
273 if (dflags & DAMAGE_NO_ARMOR)
274 return 0;
275
276 index = ArmorIndex (ent);
277 if (!index)
278 return 0;
279
280 armor = GetItemByIndex (index);
281
282 if (dflags & DAMAGE_ENERGY)
283 save = (float)ceil(((gitem_armor_t *)armor->info)->energy_protection*damage);
284 else
285 save = (float)ceil(((gitem_armor_t *)armor->info)->normal_protection*damage);
286 if (save >= client->pers.inventory[index])
287 save = client->pers.inventory[index];
288
289 if (!save)
290 return 0;
291
292 client->pers.inventory[index] -= save;
293 SpawnDamage (te_sparks, point, normal, save);
294
295 return save;
296 }
297
M_ReactToDamage(edict_t * targ,edict_t * attacker)298 void M_ReactToDamage (edict_t *targ, edict_t *attacker)
299 {
300 if (!(attacker->client) && !(attacker->svflags & SVF_MONSTER))
301 return;
302
303 if (attacker == targ || attacker == targ->enemy)
304 return;
305
306 // if we are a good guy monster and our attacker is a player
307 // or another good guy, do not get mad at them
308 if (targ->monsterinfo.aiflags & AI_GOOD_GUY)
309 {
310 if (attacker->client || (attacker->monsterinfo.aiflags & AI_GOOD_GUY))
311 return;
312 }
313
314 // we now know that we are not both good guys
315
316 // if attacker is a client, get mad at them because he's good and we're not
317 if (attacker->client)
318 {
319 targ->monsterinfo.aiflags &= ~AI_SOUND_TARGET;
320
321 // this can only happen in coop (both new and old enemies are clients)
322 // only switch if can't see the current enemy
323 if (targ->enemy && targ->enemy->client)
324 {
325 if (visible(targ, targ->enemy))
326 {
327 targ->oldenemy = attacker;
328 return;
329 }
330 targ->oldenemy = targ->enemy;
331 }
332 targ->enemy = attacker;
333 if (!(targ->monsterinfo.aiflags & AI_DUCKED))
334 FoundTarget (targ);
335 return;
336 }
337
338 // it's the same base (walk/swim/fly) type and a different classname and it's not a tank
339 // (they spray too much), get mad at them
340 if (((targ->flags & (FL_FLY|FL_SWIM)) == (attacker->flags & (FL_FLY|FL_SWIM))) &&
341 (strcmp (targ->classname, attacker->classname) != 0) &&
342 (strcmp(attacker->classname, "monster_tank") != 0) &&
343 (strcmp(attacker->classname, "monster_supertank") != 0) &&
344 (strcmp(attacker->classname, "monster_makron") != 0) &&
345 (strcmp(attacker->classname, "monster_jorg") != 0) )
346 {
347 if (targ->enemy && targ->enemy->client)
348 targ->oldenemy = targ->enemy;
349 targ->enemy = attacker;
350 if (!(targ->monsterinfo.aiflags & AI_DUCKED))
351 FoundTarget (targ);
352 }
353 // if they *meant* to shoot us, then shoot back
354 else if (attacker->enemy == targ)
355 {
356 if (targ->enemy && targ->enemy->client)
357 targ->oldenemy = targ->enemy;
358 targ->enemy = attacker;
359 if (!(targ->monsterinfo.aiflags & AI_DUCKED))
360 FoundTarget (targ);
361 }
362 // otherwise get mad at whoever they are mad at (help our buddy) unless it is us!
363 else if (attacker->enemy && attacker->enemy != targ)
364 {
365 if (targ->enemy && targ->enemy->client)
366 targ->oldenemy = targ->enemy;
367 targ->enemy = attacker->enemy;
368 if (!(targ->monsterinfo.aiflags & AI_DUCKED))
369 FoundTarget (targ);
370 }
371 }
372
CheckTeamDamage(edict_t * targ,edict_t * attacker)373 qboolean CheckTeamDamage (edict_t *targ, edict_t *attacker)
374 {
375 //FIXME make the next line real and uncomment this block
376 // if ((ability to damage a teammate == OFF) && (targ's team == attacker's team))
377 return false;
378 }
379
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)380 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)
381 {
382 gclient_t *client;
383 int take;
384 int save;
385 int asave;
386 int psave;
387 int te_sparks;
388
389 if (!targ->takedamage)
390 return;
391
392 // friendly fire avoidance
393 // if enabled you can't hurt teammates (but you can hurt yourself)
394 // knockback still occurs
395 if ((targ != attacker) && ((deathmatch->value && ((int)(dmflags->value) & (DF_MODELTEAMS | DF_SKINTEAMS))) || coop->value))
396 {
397 if (OnSameTeam (targ, attacker))
398 {
399 if ((int)(dmflags->value) & DF_NO_FRIENDLY_FIRE)
400 damage = 0;
401 else
402 mod |= MOD_FRIENDLY_FIRE;
403 }
404 }
405 meansOfDeath = mod;
406
407 // easy mode takes half damage
408 if (skill->value == 0 && deathmatch->value == 0 && targ->client)
409 {
410 damage *= 0.5f;
411 if (!damage)
412 damage = 1;
413 }
414
415 client = targ->client;
416
417 if (dflags & DAMAGE_BULLET)
418 te_sparks = TE_BULLET_SPARKS;
419 else
420 te_sparks = TE_SPARKS;
421
422 VectorNormalize(dir);
423
424 // bonus damage for suprising a monster
425 if (!(dflags & DAMAGE_RADIUS) && (targ->svflags & SVF_MONSTER) && (attacker->client) && (!targ->enemy) && (targ->health > 0))
426 damage *= 2;
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.0f * (float)knockback / mass, kvel); // the rocket jump hack...
446 else
447 VectorScale (dir, 500.0f * (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 psave = CheckPowerArmor (targ, point, normal, take, dflags);
477 take -= psave;
478
479 asave = CheckArmor (targ, point, normal, take, te_sparks, dflags);
480 take -= asave;
481
482 //treat cheat/powerup savings the same as armor
483 asave += save;
484
485 // team damage avoidance
486 if (!(dflags & DAMAGE_NO_PROTECTION) && CheckTeamDamage (targ, attacker))
487 return;
488
489 // do the damage
490 if (take)
491 {
492 if ((targ->svflags & SVF_MONSTER) || (client))
493 SpawnDamage (TE_BLOOD, point, normal, take);
494 else
495 SpawnDamage (te_sparks, point, normal, take);
496
497
498 targ->health = targ->health - take;
499
500 if (targ->health <= 0)
501 {
502 if ((targ->svflags & SVF_MONSTER) || (client))
503 targ->flags |= FL_NO_KNOCKBACK;
504 Killed (targ, inflictor, attacker, take, point);
505 return;
506 }
507 }
508
509 if (targ->svflags & SVF_MONSTER)
510 {
511 M_ReactToDamage (targ, attacker);
512 if (!(targ->monsterinfo.aiflags & AI_DUCKED) && (take))
513 {
514 targ->pain (targ, attacker, knockback, take);
515 // nightmare mode monsters don't go into pain frames often
516 if (skill->value == 3)
517 targ->pain_debounce_time = level.time + 5;
518 }
519 }
520 else if (client)
521 {
522 if (!(targ->flags & FL_GODMODE) && (take))
523 targ->pain (targ, attacker, knockback, take);
524 }
525 else if (take)
526 {
527 if (targ->pain)
528 targ->pain (targ, attacker, knockback, take);
529 }
530
531 // add to the damage inflicted on a player this frame
532 // the total will be turned into screen blends and view angle kicks
533 // at the end of the frame
534 if (client)
535 {
536 client->damage_parmor += psave;
537 client->damage_armor += asave;
538 client->damage_blood += take;
539 client->damage_knockback += knockback;
540 VectorCopy (point, client->damage_from);
541 }
542 }
543
544
545 /*
546 ============
547 T_RadiusDamage
548 ============
549 */
T_RadiusDamage(edict_t * inflictor,edict_t * attacker,float damage,edict_t * ignore,float radius,int mod)550 void T_RadiusDamage (edict_t *inflictor, edict_t *attacker, float damage, edict_t *ignore, float radius, int mod)
551 {
552 float points;
553 edict_t *ent = NULL;
554 vec3_t v;
555 vec3_t dir;
556
557 while ((ent = findradius(ent, inflictor->s.origin, radius)) != NULL)
558 {
559 if (ent == ignore)
560 continue;
561 if (!ent->takedamage)
562 continue;
563
564 VectorAdd (ent->mins, ent->maxs, v);
565 VectorMA (ent->s.origin, 0.5f, v, v);
566 VectorSubtract (inflictor->s.origin, v, v);
567 points = damage - 0.5f * VectorLength (v);
568 if (ent == attacker)
569 points = points * 0.5f;
570 if (points > 0)
571 {
572 if (CanDamage (ent, inflictor))
573 {
574 VectorSubtract (ent->s.origin, inflictor->s.origin, dir);
575 T_Damage (ent, inflictor, attacker, dir, inflictor->s.origin, vec3_origin, (int)points, (int)points, DAMAGE_RADIUS, mod);
576 }
577 }
578 }
579 }
580