g_combat.cpp 23 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722723724725726727728729730731732733734735736737738739740741742743744745746747748749750751752753754755756757758759760761762763764765766767768769770771772773774775776777778779780781782783784785786787788789790791792793794795796797798799800801802803804805806807808809810811812813814815816817818819820821822823824825826827828829830831832833834835836837838839840841842843844845846847848849850851852853854855856857858859860861862863864865866867868869870871872873874875876877878879880881882883884885886887888889890891892893894895896897898899900901902903904905906907908909910911912
  1. // Copyright (c) ZeniMax Media Inc.
  2. // Licensed under the GNU General Public License 2.0.
  3. // g_combat.c
  4. #include "g_local.h"
  5. /*
  6. ============
  7. CanDamage
  8. Returns true if the inflictor can directly damage the target. Used for
  9. explosions and melee attacks.
  10. ============
  11. */
  12. bool CanDamage(edict_t *targ, edict_t *inflictor)
  13. {
  14. vec3_t dest;
  15. trace_t trace;
  16. // bmodels need special checking because their origin is 0,0,0
  17. vec3_t inflictor_center;
  18. if (inflictor->linked)
  19. inflictor_center = (inflictor->absmin + inflictor->absmax) * 0.5f;
  20. else
  21. inflictor_center = inflictor->s.origin;
  22. if (targ->solid == SOLID_BSP)
  23. {
  24. dest = closest_point_to_box(inflictor_center, targ->absmin, targ->absmax);
  25. trace = gi.traceline(inflictor_center, dest, inflictor, MASK_SOLID);
  26. if (trace.fraction == 1.0f)
  27. return true;
  28. }
  29. vec3_t targ_center;
  30. if (targ->linked)
  31. targ_center = (targ->absmin + targ->absmax) * 0.5f;
  32. else
  33. targ_center = targ->s.origin;
  34. trace = gi.traceline(inflictor_center, targ_center, inflictor, MASK_SOLID);
  35. if (trace.fraction == 1.0f)
  36. return true;
  37. dest = targ_center;
  38. dest[0] += 15.0f;
  39. dest[1] += 15.0f;
  40. trace = gi.traceline(inflictor_center, dest, inflictor, MASK_SOLID);
  41. if (trace.fraction == 1.0f)
  42. return true;
  43. dest = targ_center;
  44. dest[0] += 15.0f;
  45. dest[1] -= 15.0f;
  46. trace = gi.traceline(inflictor_center, dest, inflictor, MASK_SOLID);
  47. if (trace.fraction == 1.0f)
  48. return true;
  49. dest = targ_center;
  50. dest[0] -= 15.0f;
  51. dest[1] += 15.0f;
  52. trace = gi.traceline(inflictor_center, dest, inflictor, MASK_SOLID);
  53. if (trace.fraction == 1.0f)
  54. return true;
  55. dest = targ_center;
  56. dest[0] -= 15.0f;
  57. dest[1] -= 15.0f;
  58. trace = gi.traceline(inflictor_center, dest, inflictor, MASK_SOLID);
  59. if (trace.fraction == 1.0f)
  60. return true;
  61. return false;
  62. }
  63. /*
  64. ============
  65. Killed
  66. ============
  67. */
  68. void Killed(edict_t *targ, edict_t *inflictor, edict_t *attacker, int damage, const vec3_t &point, mod_t mod)
  69. {
  70. if (targ->health < -999)
  71. targ->health = -999;
  72. // [Paril-KEX]
  73. if ((targ->svflags & SVF_MONSTER) && targ->monsterinfo.aiflags & AI_MEDIC)
  74. {
  75. if (targ->enemy && targ->enemy->inuse && (targ->enemy->svflags & SVF_MONSTER)) // god, I hope so
  76. {
  77. cleanupHealTarget(targ->enemy);
  78. }
  79. // clean up self
  80. targ->monsterinfo.aiflags &= ~AI_MEDIC;
  81. }
  82. targ->enemy = attacker;
  83. targ->lastMOD = mod;
  84. // [Paril-KEX] monsters call die in their damage handler
  85. if (targ->svflags & SVF_MONSTER)
  86. return;
  87. targ->die(targ, inflictor, attacker, damage, point, mod);
  88. if (targ->monsterinfo.setskin)
  89. targ->monsterinfo.setskin(targ);
  90. }
  91. /*
  92. ================
  93. SpawnDamage
  94. ================
  95. */
  96. void SpawnDamage(int type, const vec3_t &origin, const vec3_t &normal, int damage)
  97. {
  98. if (damage > 255)
  99. damage = 255;
  100. gi.WriteByte(svc_temp_entity);
  101. gi.WriteByte(type);
  102. // gi.WriteByte (damage);
  103. gi.WritePosition(origin);
  104. gi.WriteDir(normal);
  105. gi.multicast(origin, MULTICAST_PVS, false);
  106. }
  107. /*
  108. ============
  109. T_Damage
  110. targ entity that is being damaged
  111. inflictor entity that is causing the damage
  112. attacker entity that caused the inflictor to damage targ
  113. example: targ=monster, inflictor=rocket, attacker=player
  114. dir direction of the attack
  115. point point at which the damage is being inflicted
  116. normal normal vector from that point
  117. damage amount of damage being inflicted
  118. knockback force to be applied against targ as a result of the damage
  119. dflags these flags are used to control how T_Damage works
  120. DAMAGE_RADIUS damage was indirect (from a nearby explosion)
  121. DAMAGE_NO_ARMOR armor does not protect from this damage
  122. DAMAGE_ENERGY damage is from an energy based weapon
  123. DAMAGE_NO_KNOCKBACK do not affect velocity, just view angles
  124. DAMAGE_BULLET damage is from a bullet (used for ricochets)
  125. DAMAGE_NO_PROTECTION kills godmode, armor, everything
  126. ============
  127. */
  128. static int CheckPowerArmor(edict_t *ent, const vec3_t &point, const vec3_t &normal, int damage, damageflags_t dflags)
  129. {
  130. gclient_t *client;
  131. int save;
  132. item_id_t power_armor_type;
  133. int damagePerCell;
  134. int pa_te_type;
  135. int *power;
  136. int power_used;
  137. if (ent->health <= 0)
  138. return 0;
  139. if (!damage)
  140. return 0;
  141. client = ent->client;
  142. if (dflags & (DAMAGE_NO_ARMOR | DAMAGE_NO_POWER_ARMOR)) // PGM
  143. return 0;
  144. if (client)
  145. {
  146. power_armor_type = PowerArmorType(ent);
  147. power = &client->pers.inventory[IT_AMMO_CELLS];
  148. }
  149. else if (ent->svflags & SVF_MONSTER)
  150. {
  151. power_armor_type = ent->monsterinfo.power_armor_type;
  152. power = &ent->monsterinfo.power_armor_power;
  153. }
  154. else
  155. return 0;
  156. if (power_armor_type == IT_NULL)
  157. return 0;
  158. if (!*power)
  159. return 0;
  160. if (power_armor_type == IT_ITEM_POWER_SCREEN)
  161. {
  162. vec3_t vec;
  163. float dot;
  164. vec3_t forward;
  165. // only works if damage point is in front
  166. AngleVectors(ent->s.angles, forward, nullptr, nullptr);
  167. vec = point - ent->s.origin;
  168. vec.normalize();
  169. dot = vec.dot(forward);
  170. if (dot <= 0.3f)
  171. return 0;
  172. damagePerCell = 1;
  173. pa_te_type = TE_SCREEN_SPARKS;
  174. damage = damage / 3;
  175. }
  176. else
  177. {
  178. if (ctf->integer)
  179. damagePerCell = 1; // power armor is weaker in CTF
  180. else
  181. damagePerCell = 2;
  182. pa_te_type = TE_SCREEN_SPARKS;
  183. damage = (2 * damage) / 3;
  184. }
  185. // Paril: fix small amounts of damage not
  186. // being absorbed
  187. damage = max(1, damage);
  188. save = *power * damagePerCell;
  189. if (!save)
  190. return 0;
  191. // [Paril-KEX] energy damage should do more to power armor, not ETF Rifle shots.
  192. if (dflags & DAMAGE_ENERGY)
  193. save = max(1, save / 2);
  194. if (save > damage)
  195. save = damage;
  196. // [Paril-KEX] energy damage should do more to power armor, not ETF Rifle shots.
  197. if (dflags & DAMAGE_ENERGY)
  198. power_used = (save / damagePerCell) * 2;
  199. else
  200. power_used = save / damagePerCell;
  201. power_used = max(1, power_used);
  202. SpawnDamage(pa_te_type, point, normal, save);
  203. ent->powerarmor_time = level.time + 200_ms;
  204. // Paril: adjustment so that power armor
  205. // always uses damagePerCell even if it does
  206. // only a single point of damage
  207. *power = max(0, *power - max(damagePerCell, power_used));
  208. // check power armor turn-off states
  209. if (ent->client)
  210. G_CheckPowerArmor(ent);
  211. else if (!*power)
  212. {
  213. gi.sound(ent, CHAN_AUTO, gi.soundindex("misc/mon_power2.wav"), 1.f, ATTN_NORM, 0.f);
  214. gi.WriteByte(svc_temp_entity);
  215. gi.WriteByte(TE_POWER_SPLASH);
  216. gi.WriteEntity(ent);
  217. gi.WriteByte((power_armor_type == IT_ITEM_POWER_SCREEN) ? 1 : 0);
  218. gi.multicast(ent->s.origin, MULTICAST_PHS, false);
  219. }
  220. return save;
  221. }
  222. static int CheckArmor(edict_t *ent, const vec3_t &point, const vec3_t &normal, int damage, int te_sparks,
  223. damageflags_t dflags)
  224. {
  225. gclient_t *client;
  226. int save;
  227. item_id_t index;
  228. gitem_t *armor;
  229. int *power;
  230. if (!damage)
  231. return 0;
  232. // ROGUE
  233. if (dflags & (DAMAGE_NO_ARMOR | DAMAGE_NO_REG_ARMOR))
  234. // ROGUE
  235. return 0;
  236. client = ent->client;
  237. index = ArmorIndex(ent);
  238. if (!index)
  239. return 0;
  240. armor = GetItemByIndex(index);
  241. if (dflags & DAMAGE_ENERGY)
  242. save = (int) ceilf(armor->armor_info->energy_protection * damage);
  243. else
  244. save = (int) ceilf(armor->armor_info->normal_protection * damage);
  245. if (client)
  246. power = &client->pers.inventory[index];
  247. else
  248. power = &ent->monsterinfo.armor_power;
  249. if (save >= *power)
  250. save = *power;
  251. if (!save)
  252. return 0;
  253. *power -= save;
  254. if (!client && !ent->monsterinfo.armor_power)
  255. ent->monsterinfo.armor_type = IT_NULL;
  256. SpawnDamage(te_sparks, point, normal, save);
  257. return save;
  258. }
  259. void M_ReactToDamage(edict_t *targ, edict_t *attacker, edict_t *inflictor)
  260. {
  261. // pmm
  262. bool new_tesla;
  263. if (!(attacker->client) && !(attacker->svflags & SVF_MONSTER))
  264. return;
  265. //=======
  266. // ROGUE
  267. // logic for tesla - if you are hit by a tesla, and can't see who you should be mad at (attacker)
  268. // attack the tesla
  269. // also, target the tesla if it's a "new" tesla
  270. if ((inflictor) && (!strcmp(inflictor->classname, "tesla_mine")))
  271. {
  272. new_tesla = MarkTeslaArea(targ, inflictor);
  273. if ((new_tesla || brandom()) && (!targ->enemy || !targ->enemy->classname || strcmp(targ->enemy->classname, "tesla_mine")))
  274. TargetTesla(targ, inflictor);
  275. return;
  276. }
  277. // ROGUE
  278. //=======
  279. if (attacker == targ || attacker == targ->enemy)
  280. return;
  281. // if we are a good guy monster and our attacker is a player
  282. // or another good guy, do not get mad at them
  283. if (targ->monsterinfo.aiflags & AI_GOOD_GUY)
  284. {
  285. if (attacker->client || (attacker->monsterinfo.aiflags & AI_GOOD_GUY))
  286. return;
  287. }
  288. // PGM
  289. // if we're currently mad at something a target_anger made us mad at, ignore
  290. // damage
  291. if (targ->enemy && targ->monsterinfo.aiflags & AI_TARGET_ANGER)
  292. {
  293. float percentHealth;
  294. // make sure whatever we were pissed at is still around.
  295. if (targ->enemy->inuse)
  296. {
  297. percentHealth = (float) (targ->health) / (float) (targ->max_health);
  298. if (targ->enemy->inuse && percentHealth > 0.33f)
  299. return;
  300. }
  301. // remove the target anger flag
  302. targ->monsterinfo.aiflags &= ~AI_TARGET_ANGER;
  303. }
  304. // PGM
  305. // we recently switched from reacting to damage, don't do it
  306. if (targ->monsterinfo.react_to_damage_time > level.time)
  307. return;
  308. // PMM
  309. // if we're healing someone, do like above and try to stay with them
  310. if ((targ->enemy) && (targ->monsterinfo.aiflags & AI_MEDIC))
  311. {
  312. float percentHealth;
  313. percentHealth = (float) (targ->health) / (float) (targ->max_health);
  314. // ignore it some of the time
  315. if (targ->enemy->inuse && percentHealth > 0.25f)
  316. return;
  317. // remove the medic flag
  318. cleanupHealTarget(targ->enemy);
  319. targ->monsterinfo.aiflags &= ~AI_MEDIC;
  320. }
  321. // PMM
  322. // we now know that we are not both good guys
  323. targ->monsterinfo.react_to_damage_time = level.time + random_time(3_sec, 5_sec);
  324. // if attacker is a client, get mad at them because he's good and we're not
  325. if (attacker->client)
  326. {
  327. targ->monsterinfo.aiflags &= ~AI_SOUND_TARGET;
  328. // this can only happen in coop (both new and old enemies are clients)
  329. // only switch if can't see the current enemy
  330. if (targ->enemy != attacker)
  331. {
  332. if (targ->enemy && targ->enemy->client)
  333. {
  334. if (visible(targ, targ->enemy))
  335. {
  336. targ->oldenemy = attacker;
  337. return;
  338. }
  339. targ->oldenemy = targ->enemy;
  340. }
  341. // [Paril-KEX]
  342. if ((targ->svflags & SVF_MONSTER) && targ->monsterinfo.aiflags & AI_MEDIC)
  343. {
  344. if (targ->enemy && targ->enemy->inuse && (targ->enemy->svflags & SVF_MONSTER)) // god, I hope so
  345. {
  346. cleanupHealTarget(targ->enemy);
  347. }
  348. // clean up self
  349. targ->monsterinfo.aiflags &= ~AI_MEDIC;
  350. }
  351. targ->enemy = attacker;
  352. if (!(targ->monsterinfo.aiflags & AI_DUCKED))
  353. FoundTarget(targ);
  354. }
  355. return;
  356. }
  357. if (attacker->enemy == targ // if they *meant* to shoot us, then shoot back
  358. // it's the same base (walk/swim/fly) type and both don't ignore shots,
  359. // get mad at them
  360. || (((targ->flags & (FL_FLY | FL_SWIM)) == (attacker->flags & (FL_FLY | FL_SWIM))) &&
  361. (strcmp(targ->classname, attacker->classname) != 0) && !(attacker->monsterinfo.aiflags & AI_IGNORE_SHOTS) &&
  362. !(targ->monsterinfo.aiflags & AI_IGNORE_SHOTS)))
  363. {
  364. if (targ->enemy != attacker)
  365. {
  366. // [Paril-KEX]
  367. if ((targ->svflags & SVF_MONSTER) && targ->monsterinfo.aiflags & AI_MEDIC)
  368. {
  369. if (targ->enemy && targ->enemy->inuse && (targ->enemy->svflags & SVF_MONSTER)) // god, I hope so
  370. {
  371. cleanupHealTarget(targ->enemy);
  372. }
  373. // clean up self
  374. targ->monsterinfo.aiflags &= ~AI_MEDIC;
  375. }
  376. if (targ->enemy && targ->enemy->client)
  377. targ->oldenemy = targ->enemy;
  378. targ->enemy = attacker;
  379. if (!(targ->monsterinfo.aiflags & AI_DUCKED))
  380. FoundTarget(targ);
  381. }
  382. }
  383. // otherwise get mad at whoever they are mad at (help our buddy) unless it is us!
  384. else if (attacker->enemy && attacker->enemy != targ && targ->enemy != attacker->enemy)
  385. {
  386. if (targ->enemy != attacker->enemy)
  387. {
  388. // [Paril-KEX]
  389. if ((targ->svflags & SVF_MONSTER) && targ->monsterinfo.aiflags & AI_MEDIC)
  390. {
  391. if (targ->enemy && targ->enemy->inuse && (targ->enemy->svflags & SVF_MONSTER)) // god, I hope so
  392. {
  393. cleanupHealTarget(targ->enemy);
  394. }
  395. // clean up self
  396. targ->monsterinfo.aiflags &= ~AI_MEDIC;
  397. }
  398. if (targ->enemy && targ->enemy->client)
  399. targ->oldenemy = targ->enemy;
  400. targ->enemy = attacker->enemy;
  401. if (!(targ->monsterinfo.aiflags & AI_DUCKED))
  402. FoundTarget(targ);
  403. }
  404. }
  405. }
  406. // check if the two given entities are on the same team
  407. bool OnSameTeam(edict_t *ent1, edict_t *ent2)
  408. {
  409. // monsters are never on our team atm
  410. if (!ent1->client || !ent2->client)
  411. return false;
  412. // we're never on our own team
  413. else if (ent1 == ent2)
  414. return false;
  415. // [Paril-KEX] coop 'team' support
  416. if (coop->integer)
  417. return ent1->client && ent2->client;
  418. // ZOID
  419. else if (G_TeamplayEnabled() && ent1->client && ent2->client)
  420. {
  421. if (ent1->client->resp.ctf_team == ent2->client->resp.ctf_team)
  422. return true;
  423. }
  424. // ZOID
  425. return false;
  426. }
  427. // check if the two entities are on a team and that
  428. // they wouldn't damage each other
  429. bool CheckTeamDamage(edict_t *targ, edict_t *attacker)
  430. {
  431. // always damage teammates if friendly fire is enabled
  432. if (g_friendly_fire->integer)
  433. return false;
  434. return OnSameTeam(targ, attacker);
  435. }
  436. void T_Damage(edict_t *targ, edict_t *inflictor, edict_t *attacker, const vec3_t &dir, const vec3_t &point,
  437. const vec3_t &normal, int damage, int knockback, damageflags_t dflags, mod_t mod)
  438. {
  439. gclient_t *client;
  440. int take;
  441. int save;
  442. int asave;
  443. int psave;
  444. int te_sparks;
  445. bool sphere_notified; // PGM
  446. if (!targ->takedamage)
  447. return;
  448. if (g_instagib->integer && attacker->client && targ->client)
  449. {
  450. // [Kex] always kill no matter what on instagib
  451. damage = 9999;
  452. }
  453. sphere_notified = false; // PGM
  454. // friendly fire avoidance
  455. // if enabled you can't hurt teammates (but you can hurt yourself)
  456. // knockback still occurs
  457. if ((targ != attacker) && !(dflags & DAMAGE_NO_PROTECTION))
  458. {
  459. // mark as friendly fire
  460. if (OnSameTeam(targ, attacker))
  461. {
  462. mod.friendly_fire = true;
  463. // if we're not a nuke & friendly fire is disabled, just kill the damage
  464. if (!g_friendly_fire->integer && (mod.id != MOD_NUKE))
  465. damage = 0;
  466. }
  467. }
  468. // ROGUE
  469. // allow the deathmatch game to change values
  470. if (deathmatch->integer && gamerules->integer)
  471. {
  472. if (DMGame.ChangeDamage)
  473. damage = DMGame.ChangeDamage(targ, attacker, damage, mod);
  474. if (DMGame.ChangeKnockback)
  475. knockback = DMGame.ChangeKnockback(targ, attacker, knockback, mod);
  476. if (!damage)
  477. return;
  478. }
  479. // ROGUE
  480. // easy mode takes half damage
  481. if (skill->integer == 0 && deathmatch->integer == 0 && targ->client && damage)
  482. {
  483. damage /= 2;
  484. if (!damage)
  485. damage = 1;
  486. }
  487. if ( ( targ->svflags & SVF_MONSTER ) != 0 ) {
  488. damage *= ai_damage_scale->integer;
  489. } else {
  490. damage *= g_damage_scale->integer;
  491. } // mal: just for debugging...
  492. client = targ->client;
  493. // PMM - defender sphere takes half damage
  494. if (damage && (client) && (client->owned_sphere) && (client->owned_sphere->spawnflags == SPHERE_DEFENDER))
  495. {
  496. damage /= 2;
  497. if (!damage)
  498. damage = 1;
  499. }
  500. if (dflags & DAMAGE_BULLET)
  501. te_sparks = TE_BULLET_SPARKS;
  502. else
  503. te_sparks = TE_SPARKS;
  504. // bonus damage for surprising a monster
  505. if (!(dflags & DAMAGE_RADIUS) && (targ->svflags & SVF_MONSTER) && (attacker->client) &&
  506. (!targ->enemy || targ->monsterinfo.surprise_time == level.time) && (targ->health > 0))
  507. {
  508. damage *= 2;
  509. targ->monsterinfo.surprise_time = level.time;
  510. }
  511. // ZOID
  512. // strength tech
  513. damage = CTFApplyStrength(attacker, damage);
  514. // ZOID
  515. if ((targ->flags & FL_NO_KNOCKBACK) ||
  516. ((targ->flags & FL_ALIVE_KNOCKBACK_ONLY) && (!targ->deadflag || targ->dead_time != level.time)))
  517. knockback = 0;
  518. // figure momentum add
  519. if (!(dflags & DAMAGE_NO_KNOCKBACK))
  520. {
  521. if ((knockback) && (targ->movetype != MOVETYPE_NONE) && (targ->movetype != MOVETYPE_BOUNCE) &&
  522. (targ->movetype != MOVETYPE_PUSH) && (targ->movetype != MOVETYPE_STOP))
  523. {
  524. vec3_t normalized = dir.normalized();
  525. vec3_t kvel;
  526. float mass;
  527. if (targ->mass < 50)
  528. mass = 50;
  529. else
  530. mass = (float) targ->mass;
  531. if (targ->client && attacker == targ)
  532. kvel = normalized * (1600.0f * knockback / mass); // the rocket jump hack...
  533. else
  534. kvel = normalized * (500.0f * knockback / mass);
  535. targ->velocity += kvel;
  536. }
  537. }
  538. take = damage;
  539. save = 0;
  540. // check for godmode
  541. if ((targ->flags & FL_GODMODE) && !(dflags & DAMAGE_NO_PROTECTION))
  542. {
  543. take = 0;
  544. save = damage;
  545. SpawnDamage(te_sparks, point, normal, save);
  546. }
  547. // check for invincibility
  548. // ROGUE
  549. if (!(dflags & DAMAGE_NO_PROTECTION) &&
  550. (((client && client->invincible_time > level.time)) ||
  551. ((targ->svflags & SVF_MONSTER) && targ->monsterinfo.invincible_time > level.time)))
  552. // ROGUE
  553. {
  554. if (targ->pain_debounce_time < level.time)
  555. {
  556. gi.sound(targ, CHAN_ITEM, gi.soundindex("items/protect4.wav"), 1, ATTN_NORM, 0);
  557. targ->pain_debounce_time = level.time + 2_sec;
  558. }
  559. take = 0;
  560. save = damage;
  561. }
  562. // ZOID
  563. // team armor protect
  564. if (G_TeamplayEnabled() && targ->client && attacker->client &&
  565. targ->client->resp.ctf_team == attacker->client->resp.ctf_team && targ != attacker &&
  566. g_teamplay_armor_protect->integer)
  567. {
  568. psave = asave = 0;
  569. }
  570. else
  571. {
  572. // ZOID
  573. psave = CheckPowerArmor(targ, point, normal, take, dflags);
  574. take -= psave;
  575. asave = CheckArmor(targ, point, normal, take, te_sparks, dflags);
  576. take -= asave;
  577. }
  578. // treat cheat/powerup savings the same as armor
  579. asave += save;
  580. // ZOID
  581. // resistance tech
  582. take = CTFApplyResistance(targ, take);
  583. // ZOID
  584. // ZOID
  585. CTFCheckHurtCarrier(targ, attacker);
  586. // ZOID
  587. // ROGUE - this option will do damage both to the armor and person. originally for DPU rounds
  588. if (dflags & DAMAGE_DESTROY_ARMOR)
  589. {
  590. if (!(targ->flags & FL_GODMODE) && !(dflags & DAMAGE_NO_PROTECTION) &&
  591. !(client && client->invincible_time > level.time))
  592. {
  593. take = damage;
  594. }
  595. }
  596. // ROGUE
  597. // [Paril-KEX] player hit markers
  598. if (targ != attacker && attacker->client && targ->health > 0 && !((targ->svflags & SVF_DEADMONSTER) || (targ->flags & FL_NO_DAMAGE_EFFECTS)) && mod.id != MOD_TARGET_LASER)
  599. attacker->client->ps.stats[STAT_HIT_MARKER] += take + psave + asave;
  600. // do the damage
  601. if (take)
  602. {
  603. if (!(targ->flags & FL_NO_DAMAGE_EFFECTS))
  604. {
  605. // ROGUE
  606. if (targ->flags & FL_MECHANICAL)
  607. SpawnDamage(TE_ELECTRIC_SPARKS, point, normal, take);
  608. // ROGUE
  609. else if ((targ->svflags & SVF_MONSTER) || (client))
  610. {
  611. // XATRIX
  612. if (strcmp(targ->classname, "monster_gekk") == 0)
  613. SpawnDamage(TE_GREENBLOOD, point, normal, take);
  614. // XATRIX
  615. // ROGUE
  616. else if (mod.id == MOD_CHAINFIST)
  617. SpawnDamage(TE_MOREBLOOD, point, normal, 255);
  618. // ROGUE
  619. else
  620. SpawnDamage(TE_BLOOD, point, normal, take);
  621. }
  622. else
  623. SpawnDamage(te_sparks, point, normal, take);
  624. }
  625. if (!CTFMatchSetup())
  626. targ->health = targ->health - take;
  627. if ((targ->flags & FL_IMMORTAL) && targ->health <= 0)
  628. targ->health = 1;
  629. // PGM - spheres need to know who to shoot at
  630. if (client && client->owned_sphere)
  631. {
  632. sphere_notified = true;
  633. if (client->owned_sphere->pain)
  634. client->owned_sphere->pain(client->owned_sphere, attacker, 0, 0, mod);
  635. }
  636. // PGM
  637. if (targ->health <= 0)
  638. {
  639. if ((targ->svflags & SVF_MONSTER) || (client))
  640. {
  641. targ->flags |= FL_ALIVE_KNOCKBACK_ONLY;
  642. targ->dead_time = level.time;
  643. }
  644. targ->monsterinfo.damage_blood += take;
  645. targ->monsterinfo.damage_attacker = attacker;
  646. targ->monsterinfo.damage_inflictor = inflictor;
  647. targ->monsterinfo.damage_from = point;
  648. targ->monsterinfo.damage_mod = mod;
  649. targ->monsterinfo.damage_knockback += knockback;
  650. Killed(targ, inflictor, attacker, take, point, mod);
  651. return;
  652. }
  653. }
  654. // PGM - spheres need to know who to shoot at
  655. if (!sphere_notified)
  656. {
  657. if (client && client->owned_sphere)
  658. {
  659. sphere_notified = true;
  660. if (client->owned_sphere->pain)
  661. client->owned_sphere->pain(client->owned_sphere, attacker, 0, 0, mod);
  662. }
  663. }
  664. // PGM
  665. if ( targ->client ) {
  666. targ->client->last_attacker_time = level.time;
  667. }
  668. if (targ->svflags & SVF_MONSTER)
  669. {
  670. if (damage > 0)
  671. {
  672. M_ReactToDamage(targ, attacker, inflictor);
  673. targ->monsterinfo.damage_attacker = attacker;
  674. targ->monsterinfo.damage_inflictor = inflictor;
  675. targ->monsterinfo.damage_blood += take;
  676. targ->monsterinfo.damage_from = point;
  677. targ->monsterinfo.damage_mod = mod;
  678. targ->monsterinfo.damage_knockback += knockback;
  679. }
  680. if (targ->monsterinfo.setskin)
  681. targ->monsterinfo.setskin(targ);
  682. }
  683. else if (take && targ->pain)
  684. targ->pain(targ, attacker, (float) knockback, take, mod);
  685. // add to the damage inflicted on a player this frame
  686. // the total will be turned into screen blends and view angle kicks
  687. // at the end of the frame
  688. if (client)
  689. {
  690. client->damage_parmor += psave;
  691. client->damage_armor += asave;
  692. client->damage_blood += take;
  693. client->damage_knockback += knockback;
  694. client->damage_from = point;
  695. client->last_damage_time = level.time + COOP_DAMAGE_RESPAWN_TIME;
  696. if (!(dflags & DAMAGE_NO_INDICATOR) && inflictor != world && attacker != world && (take || psave || asave))
  697. {
  698. damage_indicator_t *indicator = nullptr;
  699. size_t i;
  700. for (i = 0; i < client->num_damage_indicators; i++)
  701. {
  702. if ((point - client->damage_indicators[i].from).length() < 32.f)
  703. {
  704. indicator = &client->damage_indicators[i];
  705. break;
  706. }
  707. }
  708. if (!indicator && i != MAX_DAMAGE_INDICATORS)
  709. {
  710. indicator = &client->damage_indicators[i];
  711. // for projectile direct hits, use the attacker; otherwise
  712. // use the inflictor (rocket splash should point to the rocket)
  713. indicator->from = (dflags & DAMAGE_RADIUS) ? inflictor->s.origin : attacker->s.origin;
  714. indicator->health = indicator->armor = indicator->power = 0;
  715. client->num_damage_indicators++;
  716. }
  717. if (indicator)
  718. {
  719. indicator->health += take;
  720. indicator->power += psave;
  721. indicator->armor += asave;
  722. }
  723. }
  724. }
  725. }
  726. /*
  727. ============
  728. T_RadiusDamage
  729. ============
  730. */
  731. void T_RadiusDamage(edict_t *inflictor, edict_t *attacker, float damage, edict_t *ignore, float radius, damageflags_t dflags, mod_t mod)
  732. {
  733. float points;
  734. edict_t *ent = nullptr;
  735. vec3_t v;
  736. vec3_t dir;
  737. vec3_t inflictor_center;
  738. if (inflictor->linked)
  739. inflictor_center = (inflictor->absmax + inflictor->absmin) * 0.5f;
  740. else
  741. inflictor_center = inflictor->s.origin;
  742. while ((ent = findradius(ent, inflictor_center, radius)) != nullptr)
  743. {
  744. if (ent == ignore)
  745. continue;
  746. if (!ent->takedamage)
  747. continue;
  748. if (ent->solid == SOLID_BSP && ent->linked)
  749. v = closest_point_to_box(inflictor_center, ent->absmin, ent->absmax);
  750. else
  751. {
  752. v = ent->mins + ent->maxs;
  753. v = ent->s.origin + (v * 0.5f);
  754. }
  755. v = inflictor_center - v;
  756. points = damage - 0.5f * v.length();
  757. if (ent == attacker)
  758. points = points * 0.5f;
  759. if (points > 0)
  760. {
  761. if (CanDamage(ent, inflictor))
  762. {
  763. dir = (ent->s.origin - inflictor_center).normalized();
  764. // [Paril-KEX] use closest point on bbox to explosion position
  765. // to spawn damage effect
  766. T_Damage(ent, inflictor, attacker, dir, closest_point_to_box(inflictor_center, ent->absmin, ent->absmax), dir, (int) points, (int) points,
  767. dflags | DAMAGE_RADIUS, mod);
  768. }
  769. }
  770. }
  771. }