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 #ifdef HAVE_CONFIG_H
23 #include "config.h"
24 #endif
25
26 #include "g_local.h"
27
28 /*
29 ============
30 CanDamage
31
32 Returns true if the inflictor can directly damage the target. Used for
33 explosions and melee attacks.
34 ============
35 */
CanDamage(edict_t * targ,edict_t * inflictor)36 qboolean CanDamage (edict_t *targ, edict_t *inflictor)
37 {
38 vec3_t dest;
39 trace_t trace;
40
41 // bmodels need special checking because their origin is 0,0,0
42 if (targ->movetype == MOVETYPE_PUSH)
43 {
44 VectorAdd (targ->absmin, targ->absmax, dest);
45 VectorScale (dest, 0.5, dest);
46 trace = gi.trace (inflictor->s.origin, vec3_origin, vec3_origin, dest, inflictor, MASK_SOLID);
47 if (trace.fraction == 1.0)
48 return true;
49 if (trace.ent == targ)
50 return true;
51 return false;
52 }
53
54 trace = gi.trace (inflictor->s.origin, vec3_origin, vec3_origin, targ->s.origin, 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 VectorCopy (targ->s.origin, dest);
80 dest[0] -= 15.0;
81 dest[1] -= 15.0;
82 trace = gi.trace (inflictor->s.origin, vec3_origin, vec3_origin, dest, inflictor, MASK_SOLID);
83 if (trace.fraction == 1.0)
84 return true;
85
86
87 return false;
88 }
89
90
91 /*
92 ============
93 Killed
94 ============
95 */
Killed(edict_t * targ,edict_t * inflictor,edict_t * attacker,int damage,vec3_t point)96 void Killed (edict_t *targ, edict_t *inflictor, edict_t *attacker, int damage, vec3_t point)
97 {
98 if (targ->health < -999)
99 targ->health = -999;
100
101 if (targ->monsterinfo.aiflags & AI_NPC) { //cows never really die, they just return to their
102 //original spawn points
103 //send an effect
104 gi.WriteByte (svc_temp_entity);
105 gi.WriteByte (TE_BFG_BIGEXPLOSION);
106 gi.WritePosition (targ->s.origin);
107 gi.multicast (targ->s.origin, MULTICAST_PHS);
108
109 targ->health = targ->max_health;
110 targ->s.event = EV_PLAYER_TELEPORT;
111 targ->enemy = NULL;
112 VectorCopy(targ->s.spawn_pos, targ->s.origin);
113 return;
114 }
115
116 targ->enemy = attacker;
117
118 if ((targ->svflags & SVF_MONSTER) && (targ->deadflag != DEAD_DEAD))
119 {
120 // targ->svflags |= SVF_DEADMONSTER; // now treat as a different content type
121 if (!(targ->monsterinfo.aiflags & AI_GOOD_GUY))
122 {
123 level.killed_monsters++;
124 // medics won't heal monsters that they kill themselves
125 if (strcmp(attacker->classname, "monster_medic") == 0)
126 targ->owner = attacker;
127 }
128 }
129
130 if (targ->movetype == MOVETYPE_PUSH || targ->movetype == MOVETYPE_STOP || targ->movetype == MOVETYPE_NONE)
131 { // doors, triggers, etc
132 targ->die (targ, inflictor, attacker, damage, point);
133 return;
134 }
135
136 if ((targ->svflags & SVF_MONSTER) && (targ->deadflag != DEAD_DEAD))
137 {
138 targ->touch = NULL;
139 monster_death_use (targ);
140 }
141
142 targ->die (targ, inflictor, attacker, damage, point);
143 }
144
145
146 /*
147 ================
148 SpawnDamage
149 ================
150 */
SpawnDamage(int type,vec3_t origin,vec3_t normal,int damage)151 void SpawnDamage (int type, vec3_t origin, vec3_t normal, int damage)
152 {
153 if (damage > 255)
154 damage = 255;
155 gi.WriteByte (svc_temp_entity);
156 gi.WriteByte (type);
157 // gi.WriteByte (damage);
158 gi.WritePosition (origin);
159 gi.WriteDir (normal);
160 gi.multicast (origin, MULTICAST_PVS);
161 }
162
163
164 /*
165 ============
166 T_Damage
167
168 targ entity that is being damaged
169 inflictor entity that is causing the damage
170 attacker entity that caused the inflictor to damage targ
171 example: targ=monster, inflictor=rocket, attacker=player
172
173 dir direction of the attack
174 point point at which the damage is being inflicted
175 normal normal vector from that point
176 damage amount of damage being inflicted
177 knockback force to be applied against targ as a result of the damage
178
179 dflags these flags are used to control how T_Damage works
180 DAMAGE_RADIUS damage was indirect (from a nearby explosion)
181 DAMAGE_NO_ARMOR armor does not protect from this damage
182 DAMAGE_ENERGY damage is from an energy based weapon
183 DAMAGE_NO_KNOCKBACK do not affect velocity, just view angles
184 DAMAGE_BULLET damage is from a bullet (used for ricochets)
185 DAMAGE_NO_PROTECTION kills godmode, armor, everything
186 ============
187 */
CheckArmor(edict_t * ent,vec3_t point,vec3_t normal,int damage,int te_sparks,int dflags)188 static int CheckArmor (edict_t *ent, vec3_t point, vec3_t normal, int damage, int te_sparks, int dflags)
189 {
190 gclient_t *client;
191 int save;
192 int index;
193 gitem_t *armor;
194
195 if (!damage)
196 return 0;
197
198 client = ent->client;
199
200 if (!client)
201 return 0;
202
203 if (dflags & DAMAGE_NO_ARMOR)
204 return 0;
205
206 index = ArmorIndex (ent);
207 if (!index)
208 return 0;
209
210 armor = GetItemByIndex (index);
211
212 if (dflags & DAMAGE_ENERGY)
213 save = ceil(((gitem_armor_t *)armor->info)->energy_protection*damage);
214 else
215 save = ceil(((gitem_armor_t *)armor->info)->normal_protection*damage);
216 if (save >= client->pers.inventory[index])
217 save = client->pers.inventory[index];
218
219 if (!save)
220 return 0;
221
222 client->pers.inventory[index] -= save;
223 SpawnDamage (te_sparks, point, normal, save);
224
225 return save;
226 }
227
CheckTeamDamage(edict_t * targ,edict_t * attacker)228 qboolean CheckTeamDamage (edict_t *targ, edict_t *attacker)
229 {
230 //FIXME make the next line real and uncomment this block
231 // if ((ability to damage a teammate == OFF) && (targ's team == attacker's team))
232 return false;
233 }
234
VerifyHeadShot(vec3_t point,vec3_t dir,float height,vec3_t newpoint)235 void VerifyHeadShot( vec3_t point, vec3_t dir, float height, vec3_t newpoint)
236 {
237 vec3_t normdir;
238 vec3_t normdir2;
239
240
241 VectorNormalize2(dir, normdir);
242 VectorScale( normdir, height, normdir2 );
243 VectorAdd( point, normdir2, newpoint );
244 }
245
246 #define HEAD_HEIGHT 12.0
247 #define CROUCHING_MAXS2 16
248
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)249 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)
250 {
251 gclient_t *client;
252 int take;
253 int save;
254 int asave;
255 int te_sparks;
256 int head_success = 0;
257 float z_rel;
258 int height;
259 float from_top;
260 float targ_maxs2;
261
262 if (!targ->takedamage)
263 return;
264
265 if(mod != MOD_TELEFRAG)
266 if(targ->inuse && targ->client)
267 if(targ->client->spawnprotected)
268 return;
269
270 //headshots for disruptors
271 targ_maxs2 = targ->maxs[2];
272 if (targ_maxs2 == 4)
273 targ_maxs2 = CROUCHING_MAXS2;
274
275 height = abs(targ->mins[2]) + targ_maxs2;
276
277 if (targ->client && mod == MOD_DISRUPTOR)
278 {
279 z_rel = point[2] - targ->s.origin[2];
280 from_top = targ_maxs2 - z_rel;
281
282 if (from_top < 0.0)
283 from_top = 0.0;
284
285 if ( from_top < 2*HEAD_HEIGHT )
286 {
287 vec3_t new_point;
288 VerifyHeadShot( point, dir, HEAD_HEIGHT, new_point );
289 VectorSubtract( new_point, targ->s.origin, new_point );
290
291 if ( (targ_maxs2 - new_point[2]) < HEAD_HEIGHT
292 && (abs(new_point[1])) < HEAD_HEIGHT*.8
293 && (abs(new_point[0])) < HEAD_HEIGHT*.8 ) {
294 head_success = 1;
295 }
296 }
297
298 if ( head_success )
299 {
300 damage = damage*1.8 + 1;
301 if (attacker->client)
302 mod = MOD_HEADSHOT;
303 }
304 }
305
306 // friendly fire avoidance
307 // if enabled you can't hurt teammates (but you can hurt yourself)
308 // knockback still occurs
309 if ((targ != attacker) && ((deathmatch->value && (dmflags->integer & (DF_SKINTEAMS))) || ctf->value || tca->value || cp->value))
310 {
311 if (OnSameTeam (targ, attacker) && mod != MOD_TELEFRAG) //telefrag kills no matter what
312 {
313 if (dmflags->integer & DF_NO_FRIENDLY_FIRE)
314 damage = 0;
315 else
316 mod |= MOD_FRIENDLY_FIRE;
317
318 //educate the noobs....
319 safe_centerprintf(attacker, "Stop shooting your teammates!!!");
320 }
321 }
322
323 if (targ == attacker) {
324 damage *= wep_selfdmgmulti->value;
325 }
326
327 meansOfDeath = mod;
328
329 // easy mode takes half damage
330 if (skill->value == 0 && deathmatch->value == 0 && targ->client)
331 {
332 damage *= 0.5;
333 if (!damage)
334 damage = 1;
335 }
336
337 client = targ->client;
338
339 if (dflags & DAMAGE_BULLET)
340 te_sparks = TE_BULLET_SPARKS;
341 else
342 te_sparks = TE_SPARKS;
343
344 VectorNormalize(dir);
345
346 if (targ->flags & FL_NO_KNOCKBACK)
347 knockback = 0;
348
349 // figure momentum add
350 if (!(dflags & DAMAGE_NO_KNOCKBACK))
351 {
352 if ((knockback) && (targ->movetype != MOVETYPE_NONE) && (targ->movetype != MOVETYPE_BOUNCE) && (targ->movetype != MOVETYPE_PUSH) && (targ->movetype != MOVETYPE_STOP))
353 {
354 vec3_t kvel;
355 float mass;
356
357 if (targ->mass < 50)
358 mass = 50;
359 else
360 mass = targ->mass;
361
362 if (targ->client && attacker == targ)
363 VectorScale (dir, 1600.0 * (float)knockback / mass, kvel); // the rocket jump hack...
364 else
365 VectorScale (dir, 500.0 * (float)knockback / mass, kvel);
366
367 VectorAdd (targ->velocity, kvel, targ->velocity);
368 }
369 }
370 //diminish plasma splash, as we want this to be minimal, as it's more used for tricks
371 if(mod == MOD_PLASMA_SPLASH)
372 {
373 // damage /= (1+ (int)(random() * 10.0));
374 damage /= 6; // median of formerly random 1..11
375 }
376
377 take = damage;
378 save = 0;
379
380 // check for godmode
381 if ( (targ->flags & FL_GODMODE) && !(dflags & DAMAGE_NO_PROTECTION) )
382 {
383 take = 0;
384 save = damage;
385 SpawnDamage (te_sparks, point, normal, save);
386 }
387
388 // check for invincibility
389 if ((client && client->invincible_framenum > level.framenum ) && !(dflags & DAMAGE_NO_PROTECTION))
390 {
391 if (targ->pain_debounce_time < level.time)
392 {
393 gi.sound(targ, CHAN_ITEM, gi.soundindex("items/protect4.wav"), 1, ATTN_NORM, 0);
394 targ->pain_debounce_time = level.time + 2;
395 }
396 if(mod == MOD_TRIGGER_HURT)
397 {
398 take = 0;
399 save = damage;
400 }
401 else
402 {
403 take = damage/3;
404 save = 0;
405 }
406 }
407
408 asave = CheckArmor (targ, point, normal, take, te_sparks, dflags);
409 take -= asave;
410
411 //treat cheat/powerup savings the same as armor
412 asave += save;
413
414 // team damage avoidance
415 if (!(dflags & DAMAGE_NO_PROTECTION) && CheckTeamDamage (targ, attacker))
416 return;
417
418 // do the damage
419 if (take)
420 {
421 if (client)
422 {
423 if (targ->ctype == 0) //alien, robot, human
424 SpawnDamage (TE_GREENBLOOD, point, normal, take);
425 else if (targ->ctype == 2)
426 SpawnDamage (TE_GUNSHOT, point, normal, take);
427 else
428 SpawnDamage (TE_BLOOD, point, normal, take);
429 }
430 else
431 {
432 if (targ->ctype == 0) //alien, robot, human
433 SpawnDamage (TE_GREENBLOOD, point, normal, take);
434 else if (targ->ctype == 2)
435 SpawnDamage (TE_GUNSHOT, point, normal, take);
436 else
437 SpawnDamage (TE_BLOOD, point, normal, take);
438 if(tca->value)
439 {
440 if(!(strcmp(targ->classname, "misc_redspidernode")) || !(strcmp(targ->classname, "misc_bluespidernode")))
441 safe_centerprintf(attacker, "Spider health at %i percent", 100*(targ->health-take)/600);
442 }
443 if(g_tactical->value)
444 {
445 if(!strcmp(targ->classname, "alien computer"))
446 safe_centerprintf(attacker, "Alien Computer health at %i percent", 100*(targ->health-take)/1500);
447 else if(!strcmp(targ->classname, "human computer"))
448 safe_centerprintf(attacker, "Human Computer health at %i percent", 100*(targ->health-take)/1500);
449 else if(!strcmp(targ->classname, "alien powersrc"))
450 safe_centerprintf(attacker, "Alien Power Source health at %i percent", 100*(targ->health-take)/1500);
451 else if(!strcmp(targ->classname, "human powersrc"))
452 safe_centerprintf(attacker, "Human Power Source health at %i percent", 100*(targ->health-take)/1500);
453 else if(!strcmp(targ->classname, "alien ammodepot"))
454 safe_centerprintf(attacker, "Alien Ammo Depot health at %i percent", 100*(targ->health-take)/1500);
455 else if(!strcmp(targ->classname, "human ammodepot"))
456 safe_centerprintf(attacker, "Human Ammo Depot health at %i percent", 100*(targ->health-take)/1500);
457 }
458 }
459
460 targ->health = targ->health - take;
461
462 if (targ->health <= 0)
463 {
464 if (client)
465 targ->flags |= FL_NO_KNOCKBACK;
466 Killed (targ, inflictor, attacker, take, point);
467 return;
468 }
469 }
470
471 if (client)
472 {
473 if (!(targ->flags & FL_GODMODE) && (take) && (targ->pain))
474 targ->pain (targ, attacker, knockback, take);
475 }
476 else if (take)
477 {
478 if (targ->pain)
479 targ->pain (targ, attacker, knockback, take);
480 }
481
482 // add to the damage inflicted on a player this frame
483 // the total will be turned into screen blends and view angle kicks
484 // at the end of the frame
485 if (client)
486 {
487 client->damage_armor += asave;
488 client->damage_blood += take;
489 client->damage_knockback += knockback;
490 VectorCopy (point, client->damage_from);
491 }
492 }
493
494
495 /*
496 ============
497 T_RadiusDamage
498 ============
499 */
T_RadiusDamage(edict_t * inflictor,edict_t * attacker,float damage,edict_t * ignore,float radius,int mod,int weapon)500 void T_RadiusDamage (edict_t *inflictor, edict_t *attacker, float damage, edict_t *ignore, float radius, int mod, int weapon)
501 {
502 float points;
503 edict_t *ent = NULL;
504 vec3_t v;
505 vec3_t dir;
506
507 while ((ent = findradius(ent, inflictor->s.origin, radius)) != NULL)
508 {
509 if (ent == ignore)
510 continue;
511 if (!ent->takedamage)
512 continue;
513
514 VectorAdd (ent->mins, ent->maxs, v);
515 VectorMA (ent->s.origin, 0.5, v, v);
516 VectorSubtract (inflictor->s.origin, v, v);
517 points = damage - 0.5 * VectorLength (v);
518 if (ent == attacker)
519 points = points * 0.5;
520 if (points > 0)
521 {
522 if (CanDamage (ent, inflictor))
523 {
524 VectorSubtract (ent->s.origin, inflictor->s.origin, dir);
525 T_Damage (ent, inflictor, attacker, dir, inflictor->s.origin, vec3_origin, (int)points, (int)points, DAMAGE_RADIUS, mod);
526 if (ent != attacker)
527 gi.sound (attacker, CHAN_VOICE, gi.soundindex("misc/hit.wav"), 1, ATTN_STATIC, 0);
528 if(weapon >=0)
529 attacker->client->resp.weapon_hits[weapon]++;
530 }
531 }
532 }
533 }
534