1 /*
2 * Copyright (C) 1997-2001 Id Software, Inc.
3 *
4 * This program is free software; you can redistribute it and/or modify it under
5 * the terms of the GNU General Public License as published by the Free
6 * Software Foundation; either version 2 of the License, or (at your option)
7 * any later version.
8 *
9 * This program is distributed in the hope that it will be useful, but WITHOUT
10 * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
11 * 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 along with
16 * this program; if not, write to the Free Software Foundation, Inc., 59
17 * Temple Place - Suite 330, Boston, MA 02111-1307, USA.
18 *
19 */
20 /* g_combat.c */
21
22 #include "g_local.h"
23
24 /*
25 * ============ CanDamage
26 *
27 * Returns true if the inflictor can directly damage the target. Used for
28 * explosions and melee attacks. ============
29 */
30 qboolean
CanDamage(edict_t * targ,edict_t * inflictor)31 CanDamage(edict_t * targ, edict_t * inflictor)
32 {
33 vec3_t dest;
34 trace_t trace;
35
36 /* bmodels need special checking because their origin is 0,0,0 */
37 if (targ->movetype == MOVETYPE_PUSH) {
38 VectorAdd(targ->absmin, targ->absmax, dest);
39 VectorScale(dest, 0.5, dest);
40 trace = gi.trace(inflictor->s.origin, vec3_origin, vec3_origin, dest, inflictor, MASK_SOLID);
41 if (trace.fraction == 1.0)
42 return true;
43 if (trace.ent == targ)
44 return true;
45 return false;
46 }
47 trace = gi.trace(inflictor->s.origin, vec3_origin, vec3_origin, targ->s.origin, inflictor, MASK_SOLID);
48 if (trace.fraction == 1.0)
49 return true;
50
51 VectorCopy(targ->s.origin, dest);
52 dest[0] += 15.0;
53 dest[1] += 15.0;
54 trace = gi.trace(inflictor->s.origin, vec3_origin, vec3_origin, dest, inflictor, MASK_SOLID);
55 if (trace.fraction == 1.0)
56 return true;
57
58 VectorCopy(targ->s.origin, dest);
59 dest[0] += 15.0;
60 dest[1] -= 15.0;
61 trace = gi.trace(inflictor->s.origin, vec3_origin, vec3_origin, dest, inflictor, MASK_SOLID);
62 if (trace.fraction == 1.0)
63 return true;
64
65 VectorCopy(targ->s.origin, dest);
66 dest[0] -= 15.0;
67 dest[1] += 15.0;
68 trace = gi.trace(inflictor->s.origin, vec3_origin, vec3_origin, dest, inflictor, MASK_SOLID);
69 if (trace.fraction == 1.0)
70 return true;
71
72 VectorCopy(targ->s.origin, dest);
73 dest[0] -= 15.0;
74 dest[1] -= 15.0;
75 trace = gi.trace(inflictor->s.origin, vec3_origin, vec3_origin, dest, inflictor, MASK_SOLID);
76 if (trace.fraction == 1.0)
77 return true;
78
79
80 return false;
81 }
82
83
84 /*
85 * ============ Killed ============
86 */
87 void
Killed(edict_t * targ,edict_t * inflictor,edict_t * attacker,int damage,vec3_t point)88 Killed(edict_t * targ, edict_t * inflictor, edict_t * attacker, int damage, vec3_t point)
89 {
90 if (targ->health < -999)
91 targ->health = -999;
92
93 targ->enemy = attacker;
94
95 if ((targ->svflags & SVF_MONSTER) && (targ->deadflag != DEAD_DEAD)) {
96
97 /*
98 * targ->svflags |= SVF_DEADMONSTER; // now treat as a
99 * different content type
100 */
101 if (!(targ->monsterinfo.aiflags & AI_GOOD_GUY)) {
102 level.killed_monsters++;
103 if (coop->value && attacker->client)
104 attacker->client->resp.score++;
105
106 /*
107 * medics won't heal monsters that they kill
108 * themselves
109 */
110 if (strcmp(attacker->classname, "monster_medic") == 0)
111 targ->owner = attacker;
112 }
113 }
114 if (targ->movetype == MOVETYPE_PUSH || targ->movetype == MOVETYPE_STOP || targ->movetype == MOVETYPE_NONE) { /* doors, triggers, etc */
115 targ->die(targ, inflictor, attacker, damage, point);
116 return;
117 }
118 if ((targ->svflags & SVF_MONSTER) && (targ->deadflag != DEAD_DEAD)) {
119 targ->touch = NULL;
120 monster_death_use(targ);
121 }
122 targ->die(targ, inflictor, attacker, damage, point);
123 }
124
125
126 /*
127 * ================ SpawnDamage ================
128 */
129 void
SpawnDamage(int type,vec3_t origin,vec3_t normal,int damage)130 SpawnDamage(int type, vec3_t origin, vec3_t normal, int damage)
131 {
132 if (damage > 255)
133 damage = 255;
134 gi.WriteByte(svc_temp_entity);
135 gi.WriteByte(type);
136 /* gi.WriteByte (damage); */
137 gi.WritePosition(origin);
138 gi.WriteDir(normal);
139 gi.multicast(origin, MULTICAST_PVS);
140 }
141
142
143 /*
144 * ============ T_Damage
145 *
146 * targ entity that is being damaged inflictor entity that is
147 * causing the damage attacker entity that caused the inflictor to damage
148 * targ example: targ=monster, inflictor=rocket, attacker=player
149 *
150 * dir direction of the attack point point at
151 * which the damage is being inflicted normal normal vector from
152 * that point damage amount of damage being inflicted knockback
153 * force to be applied against targ as a result of the damage
154 *
155 * dflags these flags are used to control how T_Damage works
156 * DAMAGE_RADIUS damage was indirect (from a nearby
157 * explosion) DAMAGE_NO_ARMOR armor does not protect from
158 * this damage DAMAGE_ENERGY damage is from an energy
159 * based weapon DAMAGE_NO_KNOCKBACK do not affect velocity, just
160 * view angles DAMAGE_BULLET damage is from a bullet (used
161 * for ricochets) DAMAGE_NO_PROTECTION kills godmode, armor, everything
162 * ============
163 */
164 static int
CheckPowerArmor(edict_t * ent,vec3_t point,vec3_t normal,int damage,int dflags)165 CheckPowerArmor(edict_t * ent, vec3_t point, vec3_t normal, int damage, int dflags)
166 {
167 gclient_t *client;
168 int save;
169 int power_armor_type;
170 int index;
171 int damagePerCell;
172 int pa_te_type;
173 int power;
174 int power_used;
175
176 if (!damage)
177 return 0;
178
179 client = ent->client;
180
181 if (dflags & DAMAGE_NO_ARMOR)
182 return 0;
183
184 if (client) {
185 power_armor_type = PowerArmorType(ent);
186 if (power_armor_type != POWER_ARMOR_NONE) {
187 index = ITEM_INDEX(FindItem("Cells"));
188 power = client->pers.inventory[index];
189 }
190 } else if (ent->svflags & SVF_MONSTER) {
191 power_armor_type = ent->monsterinfo.power_armor_type;
192 power = ent->monsterinfo.power_armor_power;
193 index = 0;
194 } else
195 return 0;
196
197 if (power_armor_type == POWER_ARMOR_NONE)
198 return 0;
199 if (!power)
200 return 0;
201
202 if (power_armor_type == POWER_ARMOR_SCREEN) {
203 vec3_t vec;
204 float dot;
205 vec3_t forward;
206
207 /* only works if damage point is in front */
208 AngleVectors(ent->s.angles, forward, NULL, NULL);
209 VectorSubtract(point, ent->s.origin, vec);
210 VectorNormalize(vec);
211 dot = DotProduct(vec, forward);
212 if (dot <= 0.3)
213 return 0;
214
215 damagePerCell = 1;
216 pa_te_type = TE_SCREEN_SPARKS;
217 damage = damage / 3;
218 } else {
219 damagePerCell = 2;
220 pa_te_type = TE_SHIELD_SPARKS;
221 damage = (2 * damage) / 3;
222 }
223
224 save = power * damagePerCell;
225 if (!save)
226 return 0;
227 if (save > damage)
228 save = damage;
229
230 SpawnDamage(pa_te_type, point, normal, save);
231 ent->powerarmor_time = level.time + 0.2;
232
233 power_used = save / damagePerCell;
234
235 if (client)
236 client->pers.inventory[index] -= power_used;
237 else
238 ent->monsterinfo.power_armor_power -= power_used;
239 return save;
240 }
241
242 static int
CheckArmor(edict_t * ent,vec3_t point,vec3_t normal,int damage,int te_sparks,int dflags)243 CheckArmor(edict_t * ent, vec3_t point, vec3_t normal, int damage, int te_sparks, int dflags)
244 {
245 gclient_t *client;
246 int save;
247 int index;
248 gitem_t *armor;
249
250 if (!damage)
251 return 0;
252
253 client = ent->client;
254
255 if (!client)
256 return 0;
257
258 if (dflags & DAMAGE_NO_ARMOR)
259 return 0;
260
261 index = ArmorIndex(ent);
262 if (!index)
263 return 0;
264
265 armor = GetItemByIndex(index);
266
267 if (dflags & DAMAGE_ENERGY)
268 save = ceil(((gitem_armor_t *) armor->info)->energy_protection * damage);
269 else
270 save = ceil(((gitem_armor_t *) armor->info)->normal_protection * damage);
271 if (save >= client->pers.inventory[index])
272 save = client->pers.inventory[index];
273
274 if (!save)
275 return 0;
276
277 client->pers.inventory[index] -= save;
278 SpawnDamage(te_sparks, point, normal, save);
279
280 return save;
281 }
282
283 void
M_ReactToDamage(edict_t * targ,edict_t * attacker)284 M_ReactToDamage(edict_t * targ, edict_t * attacker)
285 {
286 if (!(attacker->client) && !(attacker->svflags & SVF_MONSTER))
287 return;
288
289 if (attacker == targ || attacker == targ->enemy)
290 return;
291
292 /* if we are a good guy monster and our attacker is a player */
293 /* or another good guy, do not get mad at them */
294 if (targ->monsterinfo.aiflags & AI_GOOD_GUY) {
295 if (attacker->client || (attacker->monsterinfo.aiflags & AI_GOOD_GUY))
296 return;
297 }
298 /* we now know that we are not both good guys */
299
300 /*
301 * if attacker is a client, get mad at them because he's good and
302 * we're not
303 */
304 if (attacker->client) {
305 targ->monsterinfo.aiflags &= ~AI_SOUND_TARGET;
306
307 /*
308 * this can only happen in coop (both new and old enemies are
309 * clients)
310 */
311 /* only switch if can't see the current enemy */
312 if (targ->enemy && targ->enemy->client) {
313 if (visible(targ, targ->enemy)) {
314 targ->oldenemy = attacker;
315 return;
316 }
317 targ->oldenemy = targ->enemy;
318 }
319 targ->enemy = attacker;
320 if (!(targ->monsterinfo.aiflags & AI_DUCKED))
321 FoundTarget(targ);
322 return;
323 }
324
325 /*
326 * it's the same base (walk/swim/fly) type and a different classname
327 * and it's not a tank
328 */
329 /* (they spray too much), get mad at them */
330 if (((targ->flags & (FL_FLY | FL_SWIM)) == (attacker->flags & (FL_FLY | FL_SWIM))) &&
331 (strcmp(targ->classname, attacker->classname) != 0) &&
332 (strcmp(attacker->classname, "monster_tank") != 0) &&
333 (strcmp(attacker->classname, "monster_supertank") != 0) &&
334 (strcmp(attacker->classname, "monster_makron") != 0) &&
335 (strcmp(attacker->classname, "monster_jorg") != 0)) {
336 if (targ->enemy && targ->enemy->client)
337 targ->oldenemy = targ->enemy;
338 targ->enemy = attacker;
339 if (!(targ->monsterinfo.aiflags & AI_DUCKED))
340 FoundTarget(targ);
341 }
342 /* if they *meant* to shoot us, then shoot back */
343 else if (attacker->enemy == targ) {
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
351 /*
352 * otherwise get mad at whoever they are mad at (help our buddy)
353 * unless it is us!
354 */
355 else if (attacker->enemy && attacker->enemy != targ) {
356 if (targ->enemy && targ->enemy->client)
357 targ->oldenemy = targ->enemy;
358 targ->enemy = attacker->enemy;
359 if (!(targ->monsterinfo.aiflags & AI_DUCKED))
360 FoundTarget(targ);
361 }
362 }
363
364 qboolean
CheckTeamDamage(edict_t * targ,edict_t * attacker)365 CheckTeamDamage(edict_t * targ, edict_t * attacker)
366 {
367 /* FIXME make the next line real and uncomment this block */
368
369 /*
370 * if ((ability to damage a teammate == OFF) && (targ's team ==
371 * attacker's team))
372 */
373 return false;
374 }
375
376 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)377 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 if (OnSameTeam(targ, attacker)) {
394 if ((int)(dmflags->value) & DF_NO_FRIENDLY_FIRE)
395 damage = 0;
396 else
397 mod |= MOD_FRIENDLY_FIRE;
398 }
399 }
400 meansOfDeath = mod;
401
402 /* easy mode takes half damage */
403 if (skill->value == 0 && deathmatch->value == 0 && targ->client) {
404 damage *= 0.5;
405 if (!damage)
406 damage = 1;
407 }
408 client = targ->client;
409
410 if (dflags & DAMAGE_BULLET)
411 te_sparks = TE_BULLET_SPARKS;
412 else
413 te_sparks = TE_SPARKS;
414
415 VectorNormalize(dir);
416
417 /* bonus damage for suprising a monster */
418 if (!(dflags & DAMAGE_RADIUS) && (targ->svflags & SVF_MONSTER) && (attacker->client) && (!targ->enemy) && (targ->health > 0))
419 damage *= 2;
420
421 if (targ->flags & FL_NO_KNOCKBACK)
422 knockback = 0;
423
424 /* figure momentum add */
425 if (!(dflags & DAMAGE_NO_KNOCKBACK)) {
426 if ((knockback) && (targ->movetype != MOVETYPE_NONE) && (targ->movetype != MOVETYPE_BOUNCE) && (targ->movetype != MOVETYPE_PUSH) && (targ->movetype != MOVETYPE_STOP)) {
427 vec3_t kvel;
428 float mass;
429
430 if (targ->mass < 50)
431 mass = 50;
432 else
433 mass = targ->mass;
434
435 if (targ->client && attacker == targ)
436 VectorScale(dir, 1600.0 * (float)knockback / mass, kvel); /* the rocket jump
437 * hack... */
438 else
439 VectorScale(dir, 500.0 * (float)knockback / mass, kvel);
440
441 VectorAdd(targ->velocity, kvel, targ->velocity);
442 }
443 }
444 take = damage;
445 save = 0;
446
447 /* check for godmode */
448 if ((targ->flags & FL_GODMODE) && !(dflags & DAMAGE_NO_PROTECTION)) {
449 take = 0;
450 save = damage;
451 SpawnDamage(te_sparks, point, normal, save);
452 }
453 /* check for invincibility */
454 if ((client && client->invincible_framenum > level.framenum) && !(dflags & DAMAGE_NO_PROTECTION)) {
455 if (targ->pain_debounce_time < level.time) {
456 gi.sound(targ, CHAN_ITEM, gi.soundindex("items/protect4.wav"), 1, ATTN_NORM, 0);
457 targ->pain_debounce_time = level.time + 2;
458 }
459 take = 0;
460 save = damage;
461 }
462 psave = CheckPowerArmor(targ, point, normal, take, dflags);
463 take -= psave;
464
465 asave = CheckArmor(targ, point, normal, take, te_sparks, dflags);
466 take -= asave;
467
468 /* treat cheat/powerup savings the same as armor */
469 asave += save;
470
471 /* team damage avoidance */
472 if (!(dflags & DAMAGE_NO_PROTECTION) && CheckTeamDamage(targ, attacker))
473 return;
474
475 /* do the damage */
476 if (take) {
477 if ((targ->svflags & SVF_MONSTER) || (client))
478 SpawnDamage(TE_BLOOD, point, normal, take);
479 else
480 SpawnDamage(te_sparks, point, normal, take);
481
482
483 targ->health = targ->health - take;
484
485 if (targ->health <= 0) {
486 if ((targ->svflags & SVF_MONSTER) || (client))
487 targ->flags |= FL_NO_KNOCKBACK;
488 Killed(targ, inflictor, attacker, take, point);
489 return;
490 }
491 }
492 if (targ->svflags & SVF_MONSTER) {
493 M_ReactToDamage(targ, attacker);
494 if (!(targ->monsterinfo.aiflags & AI_DUCKED) && (take)) {
495 targ->pain(targ, attacker, knockback, take);
496
497 /*
498 * nightmare mode monsters don't go into pain frames
499 * often
500 */
501 if (skill->value == 3)
502 targ->pain_debounce_time = level.time + 5;
503 }
504 } else if (client) {
505 if (!(targ->flags & FL_GODMODE) && (take))
506 targ->pain(targ, attacker, knockback, take);
507 } else if (take) {
508 if (targ->pain)
509 targ->pain(targ, attacker, knockback, take);
510 }
511 /* add to the damage inflicted on a player this frame */
512 /* the total will be turned into screen blends and view angle kicks */
513 /* at the end of the frame */
514 if (client) {
515 client->damage_parmor += psave;
516 client->damage_armor += asave;
517 client->damage_blood += take;
518 client->damage_knockback += knockback;
519 VectorCopy(point, client->damage_from);
520 }
521 }
522
523
524 /*
525 * ============ T_RadiusDamage ============
526 */
527 void
T_RadiusDamage(edict_t * inflictor,edict_t * attacker,float damage,edict_t * ignore,float radius,int mod)528 T_RadiusDamage(edict_t * inflictor, edict_t * attacker, float damage, edict_t * ignore, float radius, int mod)
529 {
530 float points;
531 edict_t *ent = NULL;
532 vec3_t v;
533 vec3_t dir;
534
535 while ((ent = findradius(ent, inflictor->s.origin, radius)) != NULL) {
536 if (ent == ignore)
537 continue;
538 if (!ent->takedamage)
539 continue;
540
541 VectorAdd(ent->mins, ent->maxs, v);
542 VectorMA(ent->s.origin, 0.5, v, v);
543 VectorSubtract(inflictor->s.origin, v, v);
544 points = damage - 0.5 * VectorLength(v);
545 if (ent == attacker)
546 points = points * 0.5;
547 if (points > 0) {
548 if (CanDamage(ent, inflictor)) {
549 VectorSubtract(ent->s.origin, inflictor->s.origin, dir);
550 T_Damage(ent, inflictor, attacker, dir, inflictor->s.origin, vec3_origin, (int)points, (int)points, DAMAGE_RADIUS, mod);
551 }
552 }
553 }
554 }
555