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