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 qTrue 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 qBool 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 Vec3Add (targ->absMin, targ->absMax, dest);
41 Vec3Scale (dest, 0.5, dest);
42 trace = gi.trace (inflictor->s.origin, vec3Origin, vec3Origin, dest, inflictor, MASK_SOLID);
43 if (trace.fraction == 1.0)
44 return qTrue;
45 if (trace.ent == targ)
46 return qTrue;
47 return qFalse;
48 }
49
50 trace = gi.trace (inflictor->s.origin, vec3Origin, vec3Origin, targ->s.origin, inflictor, MASK_SOLID);
51 if (trace.fraction == 1.0)
52 return qTrue;
53
54 Vec3Copy (targ->s.origin, dest);
55 dest[0] += 15.0;
56 dest[1] += 15.0;
57 trace = gi.trace (inflictor->s.origin, vec3Origin, vec3Origin, dest, inflictor, MASK_SOLID);
58 if (trace.fraction == 1.0)
59 return qTrue;
60
61 Vec3Copy (targ->s.origin, dest);
62 dest[0] += 15.0;
63 dest[1] -= 15.0;
64 trace = gi.trace (inflictor->s.origin, vec3Origin, vec3Origin, dest, inflictor, MASK_SOLID);
65 if (trace.fraction == 1.0)
66 return qTrue;
67
68 Vec3Copy (targ->s.origin, dest);
69 dest[0] -= 15.0;
70 dest[1] += 15.0;
71 trace = gi.trace (inflictor->s.origin, vec3Origin, vec3Origin, dest, inflictor, MASK_SOLID);
72 if (trace.fraction == 1.0)
73 return qTrue;
74
75 Vec3Copy (targ->s.origin, dest);
76 dest[0] -= 15.0;
77 dest[1] -= 15.0;
78 trace = gi.trace (inflictor->s.origin, vec3Origin, vec3Origin, dest, inflictor, MASK_SOLID);
79 if (trace.fraction == 1.0)
80 return qTrue;
81
82
83 return qFalse;
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->floatVal && 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 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 Angles_Vectors (ent->s.angles, forward, NULL, NULL);
220 Vec3Subtract (point, ent->s.origin, vec);
221 VectorNormalizef (vec, 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
CheckArmor(edict_t * ent,vec3_t point,vec3_t normal,int damage,int te_sparks,int dflags)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
M_ReactToDamage(edict_t * targ,edict_t * attacker)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
CheckTeamDamage(edict_t * targ,edict_t * attacker)370 qBool 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 qFalse;
375 }
376
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)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->floatVal && ((int)(dmflags->floatVal) & (DF_MODELTEAMS | DF_SKINTEAMS))) || coop->floatVal))
393 {
394 if (OnSameTeam (targ, attacker))
395 {
396 if ((int)(dmflags->floatVal) & 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->floatVal == 0 && deathmatch->floatVal == 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 VectorNormalizef (dir, 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 Vec3Scale (dir, 1600.0 * (float)knockback / mass, kvel); // the rocket jump hack...
443 else
444 Vec3Scale (dir, 500.0 * (float)knockback / mass, kvel);
445
446 Vec3Add (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->floatVal == 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 Vec3Copy (point, client->damage_from);
538 }
539 }
540
541
542 /*
543 ============
544 T_RadiusDamage
545 ============
546 */
T_RadiusDamage(edict_t * inflictor,edict_t * attacker,float damage,edict_t * ignore,float radius,int mod)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 Vec3Add (ent->mins, ent->maxs, v);
562 Vec3MA (ent->s.origin, 0.5, v, v);
563 Vec3Subtract (inflictor->s.origin, v, v);
564 points = damage - 0.5 * Vec3Length (v);
565 if (ent == attacker)
566 points = points * 0.5;
567 if (points > 0)
568 {
569 if (CanDamage (ent, inflictor))
570 {
571 Vec3Subtract (ent->s.origin, inflictor->s.origin, dir);
572 T_Damage (ent, inflictor, attacker, dir, inflictor->s.origin, vec3Origin, (int)points, (int)points, DAMAGE_RADIUS, mod);
573 }
574 }
575 }
576 }
577