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