Merge branch 'martin-t/nades' into 'master'
[xonotic/xonotic-data.pk3dir.git] / qcsrc / server / g_damage.qc
1 #include "g_damage.qh"
2
3 #include "bot/api.qh"
4 #include "g_hook.qh"
5 #include "mutators/_mod.qh"
6 #include "scores.qh"
7 #include "spawnpoints.qh"
8 #include "../common/state.qh"
9 #include "../common/physics/player.qh"
10 #include "../common/t_items.qh"
11 #include "../common/vehicles/all.qh"
12 #include "../common/items/_mod.qh"
13 #include "../common/mutators/mutator/waypoints/waypointsprites.qh"
14 #include "weapons/accuracy.qh"
15 #include "weapons/csqcprojectile.qh"
16 #include "weapons/selection.qh"
17 #include "../common/constants.qh"
18 #include "../common/deathtypes/all.qh"
19 #include "../common/notifications/all.qh"
20 #include "../common/physics/movetypes/movetypes.qh"
21 #include "../common/playerstats.qh"
22 #include "../common/teams.qh"
23 #include "../common/util.qh"
24 #include <common/weapons/_all.qh>
25 #include "../lib/csqcmodel/sv_model.qh"
26 #include "../lib/warpzone/common.qh"
27
28 void UpdateFrags(entity player, int f)
29 {
30         PlayerTeamScore_AddScore(player, f);
31 }
32
33 void GiveFrags (entity attacker, entity targ, float f, int deathtype)
34 {
35         // TODO route through PlayerScores instead
36         if(gameover) return;
37
38         if(f < 0)
39         {
40                 if(targ == attacker)
41                 {
42                         // suicide
43                         PlayerScore_Add(attacker, SP_SUICIDES, 1);
44                 }
45                 else
46                 {
47                         // teamkill
48                         PlayerScore_Add(attacker, SP_KILLS, -1); // or maybe add a teamkills field?
49                 }
50         }
51         else
52         {
53                 // regular frag
54                 PlayerScore_Add(attacker, SP_KILLS, 1);
55                 if(targ.playerid)
56                         PS_GR_P_ADDVAL(attacker, sprintf("kills-%d", targ.playerid), 1);
57         }
58
59         PlayerScore_Add(targ, SP_DEATHS, 1);
60
61         if(targ != attacker) // not for suicides
62         if(g_weaponarena_random)
63         {
64                 // after a frag, exchange the current weapon (or the culprit, if detectable) by a new random weapon
65                 Weapon culprit = DEATH_WEAPONOF(deathtype);
66                 if(!culprit) culprit = PS(attacker).m_weapon;
67                 else if(!(attacker.weapons & (culprit.m_wepset))) culprit = PS(attacker).m_weapon;
68
69                 if(g_weaponarena_random_with_blaster && culprit == WEP_BLASTER) // WEAPONTODO: Shouldn't this be in a mutator?
70                 {
71                         // no exchange
72                 }
73                 else
74                 {
75                         if(!GiveFrags_randomweapons)
76                         {
77                                 GiveFrags_randomweapons = new(GiveFrags_randomweapons);
78                         }
79
80                         if(warmup_stage)
81                                 GiveFrags_randomweapons.weapons = WARMUP_START_WEAPONS;
82                         else
83                                 GiveFrags_randomweapons.weapons = start_weapons;
84
85                         // all others (including the culprit): remove
86                         GiveFrags_randomweapons.weapons &= ~attacker.weapons;
87                         GiveFrags_randomweapons.weapons &= ~(culprit.m_wepset);
88
89                         // among the remaining ones, choose one by random
90                         W_RandomWeapons(GiveFrags_randomweapons, 1);
91
92                         if(GiveFrags_randomweapons.weapons)
93                         {
94                                 attacker.weapons |= GiveFrags_randomweapons.weapons;
95                                 attacker.weapons &= ~(culprit.m_wepset);
96                         }
97                 }
98
99                 // after a frag, choose another random weapon set
100                 if (!(attacker.weapons & WepSet_FromWeapon(PS(attacker).m_weapon)))
101                         W_SwitchWeapon_Force(attacker, w_getbestweapon(attacker));
102         }
103
104         // FIXME fix the mess this is (we have REAL points now!)
105         if(MUTATOR_CALLHOOK(GiveFragsForKill, attacker, targ, f))
106                 f = M_ARGV(2, float);
107
108         attacker.totalfrags += f;
109
110         if(f)
111                 UpdateFrags(attacker, f);
112 }
113
114 .entity kh_next;
115
116 string AppendItemcodes(string s, entity player)
117 {
118         int w = PS(player).m_weapon.m_id;
119         //if(w == 0)
120         //      w = player.switchweapon;
121         if(w == 0)
122                 w = player.cnt; // previous weapon!
123         s = strcat(s, ftos(w));
124         if(time < player.strength_finished)
125                 s = strcat(s, "S");
126         if(time < player.invincible_finished)
127                 s = strcat(s, "I");
128         if(player.flagcarried != NULL)
129                 s = strcat(s, "F");
130         if(PHYS_INPUT_BUTTON_CHAT(player))
131                 s = strcat(s, "T");
132         if(player.kh_next)
133                 s = strcat(s, "K");
134         return s;
135 }
136
137 void LogDeath(string mode, int deathtype, entity killer, entity killed)
138 {
139         string s;
140         if(!autocvar_sv_eventlog)
141                 return;
142         s = strcat(":kill:", mode);
143         s = strcat(s, ":", ftos(killer.playerid));
144         s = strcat(s, ":", ftos(killed.playerid));
145         s = strcat(s, ":type=", Deathtype_Name(deathtype));
146         s = strcat(s, ":items=");
147         s = AppendItemcodes(s, killer);
148         if(killed != killer)
149         {
150                 s = strcat(s, ":victimitems=");
151                 s = AppendItemcodes(s, killed);
152         }
153         GameLogEcho(s);
154 }
155
156 void Obituary_SpecialDeath(
157         entity notif_target,
158         float murder,
159         int deathtype,
160         string s1, string s2, string s3,
161         float f1, float f2, float f3)
162 {
163         if(DEATH_ISSPECIAL(deathtype))
164         {
165                 entity deathent = Deathtypes_from(deathtype - DT_FIRST);
166                 if (!deathent) { backtrace("Obituary_SpecialDeath: Could not find deathtype entity!\n"); return; }
167
168                 if(g_cts && deathtype == DEATH_KILL.m_id)
169                         return; // TODO: somehow put this in CTS gamemode file!
170
171                 if(murder)
172                 {
173                         if(deathent.death_msgmurder)
174                         {
175                                 Send_Notification_WOCOVA(
176                                         NOTIF_ONE,
177                                         notif_target,
178                                         MSG_MULTI,
179                                         deathent.death_msgmurder,
180                                         s1, s2, s3, "",
181                                         f1, f2, f3, 0
182                                 );
183                                 Send_Notification_WOCOVA(
184                                         NOTIF_ALL_EXCEPT,
185                                         notif_target,
186                                         MSG_INFO,
187                                         deathent.death_msgmurder.nent_msginfo,
188                                         s1, s2, s3, "",
189                                         f1, f2, f3, 0
190                                 );
191                         }
192                 }
193                 else
194                 {
195                         if(deathent.death_msgself)
196                         {
197                                 Send_Notification_WOCOVA(
198                                         NOTIF_ONE,
199                                         notif_target,
200                                         MSG_MULTI,
201                                         deathent.death_msgself,
202                                         s1, s2, s3, "",
203                                         f1, f2, f3, 0
204                                 );
205                                 Send_Notification_WOCOVA(
206                                         NOTIF_ALL_EXCEPT,
207                                         notif_target,
208                                         MSG_INFO,
209                                         deathent.death_msgself.nent_msginfo,
210                                         s1, s2, s3, "",
211                                         f1, f2, f3, 0
212                                 );
213                         }
214                 }
215         }
216         else { backtrace("Obituary_SpecialDeath called without a special deathtype?\n"); return; }
217 }
218
219 float Obituary_WeaponDeath(
220         entity notif_target,
221         float murder,
222         int deathtype,
223         string s1, string s2, string s3,
224         float f1, float f2)
225 {
226         Weapon death_weapon = DEATH_WEAPONOF(deathtype);
227         if (death_weapon != WEP_Null)
228         {
229                 w_deathtype = deathtype;
230                 Notification death_message = ((murder) ? death_weapon.wr_killmessage(death_weapon) : death_weapon.wr_suicidemessage(death_weapon));
231                 w_deathtype = false;
232
233                 if (death_message)
234                 {
235                         Send_Notification_WOCOVA(
236                                 NOTIF_ONE,
237                                 notif_target,
238                                 MSG_MULTI,
239                                 death_message,
240                                 s1, s2, s3, "",
241                                 f1, f2, 0, 0
242                         );
243                         // send the info part to everyone
244                         Send_Notification_WOCOVA(
245                                 NOTIF_ALL_EXCEPT,
246                                 notif_target,
247                                 MSG_INFO,
248                                 death_message.nent_msginfo,
249                                 s1, s2, s3, "",
250                                 f1, f2, 0, 0
251                         );
252                 }
253                 else
254                 {
255                         LOG_TRACEF(
256                                 "Obituary_WeaponDeath(): ^1Deathtype ^7(%d)^1 has no notification for weapon %d!\n",
257                                 deathtype,
258                                 death_weapon
259                         );
260                 }
261
262                 return true;
263         }
264         return false;
265 }
266
267 bool frag_centermessage_override(entity attacker, entity targ, int deathtype, int kill_count_to_attacker, int kill_count_to_target)
268 {
269         if(deathtype == DEATH_FIRE.m_id)
270         {
271                 Send_Notification(NOTIF_ONE, attacker, MSG_CHOICE, CHOICE_FRAG_FIRE, targ.netname, kill_count_to_attacker, (IS_BOT_CLIENT(targ) ? -1 : targ.ping));
272                 Send_Notification(NOTIF_ONE, targ, MSG_CHOICE, CHOICE_FRAGGED_FIRE, attacker.netname, kill_count_to_target, attacker.health, attacker.armorvalue, (IS_BOT_CLIENT(attacker) ? -1 : attacker.ping));
273                 return true;
274         }
275
276         return MUTATOR_CALLHOOK(FragCenterMessage, attacker, targ, deathtype, kill_count_to_attacker, kill_count_to_target);
277 }
278
279 .int buffs = _STAT(BUFFS); // TODO: remove
280 entity buff_FirstFromFlags(int _buffs);
281 void Obituary(entity attacker, entity inflictor, entity targ, int deathtype)
282 {
283         // Sanity check
284         if (!IS_PLAYER(targ)) { backtrace("Obituary called on non-player?!\n"); return; }
285
286         // Declarations
287         float notif_firstblood = false;
288         float kill_count_to_attacker, kill_count_to_target;
289
290         // Set final information for the death
291         targ.death_origin = targ.origin;
292         if(targ != attacker) { targ.killer_origin = attacker.origin; }
293         string deathlocation = (autocvar_notification_server_allows_location ? NearestLocation(targ.death_origin) : "");
294
295         #ifdef NOTIFICATIONS_DEBUG
296         Debug_Notification(
297                 sprintf(
298                         "Obituary(%s, %s, %s, %s = %d);\n",
299                         attacker.netname,
300                         inflictor.netname,
301                         targ.netname,
302                         Deathtype_Name(deathtype),
303                         deathtype
304                 )
305         );
306         #endif
307
308         // =======
309         // SUICIDE
310         // =======
311         if(targ == attacker)
312         {
313                 if(DEATH_ISSPECIAL(deathtype))
314                 {
315                         if(deathtype == DEATH_TEAMCHANGE.m_id || deathtype == DEATH_AUTOTEAMCHANGE.m_id)
316                         {
317                                 Obituary_SpecialDeath(targ, false, deathtype, targ.netname, deathlocation, "", targ.team, 0, 0);
318                         }
319                         else
320                         {
321                                 switch(DEATH_ENT(deathtype))
322                                 {
323                                         case DEATH_MIRRORDAMAGE:
324                                         {
325                                                 Obituary_SpecialDeath(targ, false, deathtype, targ.netname, deathlocation, "", targ.killcount, 0, 0);
326                                                 break;
327                                         }
328
329                                         default:
330                                         {
331                                                 Obituary_SpecialDeath(targ, false, deathtype, targ.netname, deathlocation, "", targ.killcount, 0, 0);
332                                                 break;
333                                         }
334                                 }
335                         }
336                 }
337                 else if (!Obituary_WeaponDeath(targ, false, deathtype, targ.netname, deathlocation, "", targ.killcount, 0))
338                 {
339                         backtrace("SUICIDE: what the hell happened here?\n");
340                         return;
341                 }
342                 LogDeath("suicide", deathtype, targ, targ);
343                 if(deathtype != DEATH_AUTOTEAMCHANGE.m_id) // special case: don't negate frags if auto switched
344                         GiveFrags(attacker, targ, -1, deathtype);
345         }
346
347         // ======
348         // MURDER
349         // ======
350         else if(IS_PLAYER(attacker))
351         {
352                 if(SAME_TEAM(attacker, targ))
353                 {
354                         LogDeath("tk", deathtype, attacker, targ);
355                         GiveFrags(attacker, targ, -1, deathtype);
356
357                         attacker.killcount = 0;
358
359                         Send_Notification(NOTIF_ONE, attacker, MSG_CENTER, CENTER_DEATH_TEAMKILL_FRAG, targ.netname);
360                         Send_Notification(NOTIF_ONE, targ, MSG_CENTER, CENTER_DEATH_TEAMKILL_FRAGGED, attacker.netname);
361                         Send_Notification(NOTIF_ALL, NULL, MSG_INFO, APP_TEAM_NUM(targ.team, INFO_DEATH_TEAMKILL), targ.netname, attacker.netname, deathlocation, targ.killcount);
362
363                         // In this case, the death message will ALWAYS be "foo was betrayed by bar"
364                         // No need for specific death/weapon messages...
365                 }
366                 else
367                 {
368                         LogDeath("frag", deathtype, attacker, targ);
369                         GiveFrags(attacker, targ, 1, deathtype);
370
371                         attacker.taunt_soundtime = time + 1;
372                         attacker.killcount = attacker.killcount + 1;
373
374                         #define SPREE_ITEM(counta,countb,center,normal,gentle) \
375                                 case counta: \
376                                 { \
377                                         Send_Notification(NOTIF_ONE, attacker, MSG_ANNCE, ANNCE_KILLSTREAK_##countb); \
378                                         PS_GR_P_ADDVAL(attacker, PLAYERSTATS_ACHIEVEMENT_KILL_SPREE_##counta, 1); \
379                                         break; \
380                                 }
381                         switch(attacker.killcount)
382                         {
383                                 KILL_SPREE_LIST
384                                 default: break;
385                         }
386                         #undef SPREE_ITEM
387
388                         if(!checkrules_firstblood)
389                         {
390                                 checkrules_firstblood = true;
391                                 notif_firstblood = true; // modify the current messages so that they too show firstblood information
392                                 PS_GR_P_ADDVAL(attacker, PLAYERSTATS_ACHIEVEMENT_FIRSTBLOOD, 1);
393                                 PS_GR_P_ADDVAL(targ, PLAYERSTATS_ACHIEVEMENT_FIRSTVICTIM, 1);
394
395                                 // tell spree_inf and spree_cen that this is a first-blood and first-victim event
396                                 kill_count_to_attacker = -1;
397                                 kill_count_to_target = -2;
398                         }
399                         else
400                         {
401                                 kill_count_to_attacker = attacker.killcount;
402                                 kill_count_to_target = 0;
403                         }
404
405                         if(targ.istypefrag)
406                         {
407                                 Send_Notification(
408                                         NOTIF_ONE,
409                                         attacker,
410                                         MSG_CHOICE,
411                                         CHOICE_TYPEFRAG,
412                                         targ.netname,
413                                         kill_count_to_attacker,
414                                         (IS_BOT_CLIENT(targ) ? -1 : targ.ping)
415                                 );
416                                 Send_Notification(
417                                         NOTIF_ONE,
418                                         targ,
419                                         MSG_CHOICE,
420                                         CHOICE_TYPEFRAGGED,
421                                         attacker.netname,
422                                         kill_count_to_target,
423                                         attacker.health,
424                                         attacker.armorvalue,
425                                         (IS_BOT_CLIENT(attacker) ? -1 : attacker.ping)
426                                 );
427                         }
428                         else if(!frag_centermessage_override(attacker, targ, deathtype, kill_count_to_attacker, kill_count_to_target))
429                         {
430                                 Send_Notification(
431                                         NOTIF_ONE,
432                                         attacker,
433                                         MSG_CHOICE,
434                                         CHOICE_FRAG,
435                                         targ.netname,
436                                         kill_count_to_attacker,
437                                         (IS_BOT_CLIENT(targ) ? -1 : targ.ping)
438                                 );
439                                 Send_Notification(
440                                         NOTIF_ONE,
441                                         targ,
442                                         MSG_CHOICE,
443                                         CHOICE_FRAGGED,
444                                         attacker.netname,
445                                         kill_count_to_target,
446                                         attacker.health,
447                                         attacker.armorvalue,
448                                         (IS_BOT_CLIENT(attacker) ? -1 : attacker.ping)
449                                 );
450                         }
451
452                         int f3 = 0;
453                         if(deathtype == DEATH_BUFF.m_id)
454                                 f3 = buff_FirstFromFlags(attacker.buffs).m_id;
455
456                         if (!Obituary_WeaponDeath(targ, true, deathtype, targ.netname, attacker.netname, deathlocation, targ.killcount, kill_count_to_attacker))
457                                 Obituary_SpecialDeath(targ, true, deathtype, targ.netname, attacker.netname, deathlocation, targ.killcount, kill_count_to_attacker, f3);
458                 }
459         }
460
461         // =============
462         // ACCIDENT/TRAP
463         // =============
464         else
465         {
466                 switch(DEATH_ENT(deathtype))
467                 {
468                         // For now, we're just forcing HURTTRIGGER to behave as "DEATH_VOID" and giving it no special options...
469                         // Later on you will only be able to make custom messages using DEATH_CUSTOM,
470                         // and there will be a REAL DEATH_VOID implementation which mappers will use.
471                         case DEATH_HURTTRIGGER:
472                         {
473                                 Obituary_SpecialDeath(targ, false, deathtype,
474                                         targ.netname,
475                                         inflictor.message,
476                                         deathlocation,
477                                         targ.killcount,
478                                         0,
479                                         0);
480                                 break;
481                         }
482
483                         case DEATH_CUSTOM:
484                         {
485                                 Obituary_SpecialDeath(targ, false, deathtype,
486                                         targ.netname,
487                                         ((strstrofs(deathmessage, "%", 0) < 0) ? strcat("%s ", deathmessage) : deathmessage),
488                                         deathlocation,
489                                         targ.killcount,
490                                         0,
491                                         0);
492                                 break;
493                         }
494
495                         default:
496                         {
497                                 Obituary_SpecialDeath(targ, false, deathtype, targ.netname, deathlocation, "", targ.killcount, 0, 0);
498                                 break;
499                         }
500                 }
501
502                 LogDeath("accident", deathtype, targ, targ);
503                 GiveFrags(targ, targ, -1, deathtype);
504
505                 if(PlayerScore_Add(targ, SP_SCORE, 0) == -5)
506                 {
507                         Send_Notification(NOTIF_ONE, targ, MSG_ANNCE, ANNCE_ACHIEVEMENT_BOTLIKE);
508                         PS_GR_P_ADDVAL(attacker, PLAYERSTATS_ACHIEVEMENT_BOTLIKE, 1);
509                 }
510         }
511
512         // reset target kill count
513         if(targ.killcount) { targ.killcount = 0; }
514 }
515
516 void Ice_Think(entity this)
517 {
518         if(!STAT(FROZEN, this.owner) || this.owner.iceblock != this)
519         {
520                 delete(this);
521                 return;
522         }
523         setorigin(this, this.owner.origin - '0 0 16');
524         this.nextthink = time;
525 }
526
527 void Freeze (entity targ, float freeze_time, float frozen_type, float show_waypoint)
528 {
529         if(!IS_PLAYER(targ) && !IS_MONSTER(targ)) // only specified entities can be freezed
530                 return;
531
532         if(STAT(FROZEN, targ))
533                 return;
534
535         float targ_maxhealth = ((IS_MONSTER(targ)) ? targ.max_health : start_health);
536
537         STAT(FROZEN, targ) = frozen_type;
538         targ.revive_progress = ((frozen_type == 3) ? 1 : 0);
539         targ.health = ((frozen_type == 3) ? targ_maxhealth : 1);
540         targ.revive_speed = freeze_time;
541         if(targ.bot_attack)
542                 IL_REMOVE(g_bot_targets, targ);
543         targ.bot_attack = false;
544
545         entity ice = new(ice);
546         ice.owner = targ;
547         ice.scale = targ.scale;
548         setthink(ice, Ice_Think);
549         ice.nextthink = time;
550         ice.frame = floor(random() * 21); // ice model has 20 different looking frames
551         setmodel(ice, MDL_ICE);
552         ice.alpha = 1;
553         ice.colormod = Team_ColorRGB(targ.team);
554         ice.glowmod = ice.colormod;
555         targ.iceblock = ice;
556         targ.revival_time = 0;
557
558         Ice_Think(ice);
559
560         RemoveGrapplingHook(targ);
561
562         FOREACH_CLIENT(IS_PLAYER(it) && it.hook.aiment == targ, LAMBDA(RemoveGrapplingHook(it)));
563
564         // add waypoint
565         if(show_waypoint)
566                 WaypointSprite_Spawn(WP_Frozen, 0, 0, targ, '0 0 64', NULL, targ.team, targ, waypointsprite_attached, true, RADARICON_WAYPOINT);
567 }
568
569 void Unfreeze (entity targ)
570 {
571         if(!STAT(FROZEN, targ))
572                 return;
573
574         if(STAT(FROZEN, targ) && STAT(FROZEN, targ) != 3) // only reset health if target was frozen
575         {
576                 targ.health = ((IS_PLAYER(targ)) ? start_health : targ.max_health);
577                 targ.pauseregen_finished = time + autocvar_g_balance_pause_health_regen;
578         }
579
580         STAT(FROZEN, targ) = 0;
581         targ.revive_progress = 0;
582         targ.revival_time = time;
583         if(!targ.bot_attack)
584                 IL_PUSH(g_bot_targets, targ);
585         targ.bot_attack = true;
586
587         WaypointSprite_Kill(targ.waypointsprite_attached);
588
589         FOREACH_CLIENT(IS_PLAYER(it) && it.hook.aiment == targ, LAMBDA(RemoveGrapplingHook(it)));
590
591         // remove the ice block
592         if(targ.iceblock)
593                 delete(targ.iceblock);
594         targ.iceblock = NULL;
595 }
596
597 void Damage (entity targ, entity inflictor, entity attacker, float damage, int deathtype, vector hitloc, vector force)
598 {
599         float mirrordamage;
600         float mirrorforce;
601         float complainteamdamage = 0;
602         entity attacker_save;
603         mirrordamage = 0;
604         mirrorforce = 0;
605
606         if (gameover || targ.killcount == FRAGS_SPECTATOR)
607                 return;
608
609     damage_targ = targ;
610     damage_inflictor = inflictor;
611     damage_attacker = attacker;
612         attacker_save = attacker;
613
614         if(IS_PLAYER(targ))
615                 if(targ.hook)
616                         if(targ.hook.aiment)
617                                 if(targ.hook.aiment == attacker)
618                                         RemoveGrapplingHook(targ); // STOP THAT, you parasite!
619
620         // special rule: gravity bomb does not hit team mates (other than for disconnecting the hook)
621         if(DEATH_ISWEAPON(deathtype, WEP_HOOK) || DEATH_ISWEAPON(deathtype, WEP_TUBA))
622         {
623                 if(IS_PLAYER(targ) && SAME_TEAM(targ, attacker))
624                 {
625                         return;
626                 }
627         }
628
629         if(deathtype == DEATH_KILL.m_id || deathtype == DEATH_TEAMCHANGE.m_id || deathtype == DEATH_AUTOTEAMCHANGE.m_id)
630         {
631                 // exit the vehicle before killing (fixes a crash)
632                 if(IS_PLAYER(targ) && targ.vehicle)
633                         vehicles_exit(targ.vehicle, VHEF_RELEASE);
634
635                 // These are ALWAYS lethal
636                 // No damage modification here
637                 // Instead, prepare the victim for his death...
638                 targ.armorvalue = 0;
639                 targ.spawnshieldtime = 0;
640                 targ.health = 0.9; // this is < 1
641                 targ.flags -= targ.flags & FL_GODMODE;
642                 damage = 100000;
643         }
644         else if(deathtype == DEATH_MIRRORDAMAGE.m_id || deathtype == DEATH_NOAMMO.m_id)
645         {
646                 // no processing
647         }
648         else
649         {
650                 // nullify damage if teamplay is on
651                 if(deathtype != DEATH_TELEFRAG.m_id)
652                 if(IS_PLAYER(attacker))
653                 {
654                         if(IS_PLAYER(targ) && targ != attacker && (IS_INDEPENDENT_PLAYER(attacker) || IS_INDEPENDENT_PLAYER(targ)))
655                         {
656                                 damage = 0;
657                                 force = '0 0 0';
658                         }
659                         else if(SAME_TEAM(attacker, targ))
660                         {
661                                 if(autocvar_teamplay_mode == 1)
662                                         damage = 0;
663                                 else if(attacker != targ)
664                                 {
665                                         if(autocvar_teamplay_mode == 3)
666                                                 damage = 0;
667                                         else if(autocvar_teamplay_mode == 4)
668                                         {
669                                                 if(IS_PLAYER(targ) && !IS_DEAD(targ))
670                                                 {
671                                                         attacker.dmg_team = attacker.dmg_team + damage;
672                                                         complainteamdamage = attacker.dmg_team - autocvar_g_teamdamage_threshold;
673                                                         if(complainteamdamage > 0)
674                                                                 mirrordamage = autocvar_g_mirrordamage * complainteamdamage;
675                                                         mirrorforce = autocvar_g_mirrordamage * vlen(force);
676                                                         damage = autocvar_g_friendlyfire * damage;
677                                                         // mirrordamage will be used LATER
678
679                                                         if(autocvar_g_mirrordamage_virtual)
680                                                         {
681                                                                 vector v  = healtharmor_applydamage(attacker.armorvalue, autocvar_g_balance_armor_blockpercent, deathtype, mirrordamage);
682                                                                 attacker.dmg_take += v.x;
683                                                                 attacker.dmg_save += v.y;
684                                                                 attacker.dmg_inflictor = inflictor;
685                                                                 mirrordamage = v.z;
686                                                                 mirrorforce = 0;
687                                                         }
688
689                                                         if(autocvar_g_friendlyfire_virtual)
690                                                         {
691                                                                 vector v = healtharmor_applydamage(targ.armorvalue, autocvar_g_balance_armor_blockpercent, deathtype, damage);
692                                                                 targ.dmg_take += v.x;
693                                                                 targ.dmg_save += v.y;
694                                                                 targ.dmg_inflictor = inflictor;
695                                                                 damage = 0;
696                                                                 if(!autocvar_g_friendlyfire_virtual_force)
697                                                                         force = '0 0 0';
698                                                         }
699                                                 }
700                                                 else
701                                                         damage = 0;
702                                         }
703                                 }
704                         }
705                 }
706
707                 if (!DEATH_ISSPECIAL(deathtype))
708                 {
709                         damage *= g_weapondamagefactor;
710                         mirrordamage *= g_weapondamagefactor;
711                         complainteamdamage *= g_weapondamagefactor;
712                         force = force * g_weaponforcefactor;
713                         mirrorforce *= g_weaponforcefactor;
714                 }
715
716                 // should this be changed at all? If so, in what way?
717                 MUTATOR_CALLHOOK(PlayerDamage_Calculate, inflictor, attacker, targ, deathtype, damage, mirrordamage, force);
718                 damage = M_ARGV(4, float);
719                 mirrordamage = M_ARGV(5, float);
720                 force = M_ARGV(6, vector);
721
722                 if(STAT(FROZEN, targ))
723                 if(deathtype != DEATH_HURTTRIGGER.m_id && deathtype != DEATH_TEAMCHANGE.m_id && deathtype != DEATH_AUTOTEAMCHANGE.m_id)
724                 {
725                         if(autocvar_g_frozen_revive_falldamage > 0)
726                         if(deathtype == DEATH_FALL.m_id)
727                         if(damage >= autocvar_g_frozen_revive_falldamage)
728                         {
729                                 Unfreeze(targ);
730                                 targ.health = autocvar_g_frozen_revive_falldamage_health;
731                                 Send_Effect(EFFECT_ICEORGLASS, targ.origin, '0 0 0', 3);
732                                 Send_Notification(NOTIF_ALL, NULL, MSG_INFO, INFO_FREEZETAG_REVIVED_FALL, targ.netname);
733                                 Send_Notification(NOTIF_ONE, targ, MSG_CENTER, CENTER_FREEZETAG_REVIVE_SELF);
734                         }
735
736                         damage = 0;
737                         force *= autocvar_g_frozen_force;
738                 }
739
740                 if(STAT(FROZEN, targ) && deathtype == DEATH_HURTTRIGGER.m_id && !autocvar_g_frozen_damage_trigger)
741                 {
742                         Send_Effect(EFFECT_TELEPORT, targ.origin, '0 0 0', 1);
743
744                         entity spot = SelectSpawnPoint (targ, false);
745
746                         if(spot)
747                         {
748                                 damage = 0;
749                                 targ.deadflag = DEAD_NO;
750
751                                 targ.angles = spot.angles;
752
753                                 targ.effects = 0;
754                                 targ.effects |= EF_TELEPORT_BIT;
755
756                                 targ.angles_z = 0; // never spawn tilted even if the spot says to
757                                 targ.fixangle = true; // turn this way immediately
758                                 targ.velocity = '0 0 0';
759                                 targ.avelocity = '0 0 0';
760                                 targ.punchangle = '0 0 0';
761                                 targ.punchvector = '0 0 0';
762                                 targ.oldvelocity = targ.velocity;
763
764                                 targ.spawnorigin = spot.origin;
765                                 setorigin(targ, spot.origin + '0 0 1' * (1 - targ.mins.z - 24));
766                                 // don't reset back to last position, even if new position is stuck in solid
767                                 targ.oldorigin = targ.origin;
768                                 targ.prevorigin = targ.origin;
769
770                                 Send_Effect(EFFECT_TELEPORT, targ.origin, '0 0 0', 1);
771                         }
772                 }
773
774                 if(!g_instagib)
775                 {
776                         // apply strength multiplier
777                         if (attacker.items & ITEM_Strength.m_itemid)
778                         {
779                                 if(targ == attacker)
780                                 {
781                                         damage = damage * autocvar_g_balance_powerup_strength_selfdamage;
782                                         force = force * autocvar_g_balance_powerup_strength_selfforce;
783                                 }
784                                 else
785                                 {
786                                         damage = damage * autocvar_g_balance_powerup_strength_damage;
787                                         force = force * autocvar_g_balance_powerup_strength_force;
788                                 }
789                         }
790
791                         // apply invincibility multiplier
792                         if (targ.items & ITEM_Shield.m_itemid)
793                                 damage = damage * autocvar_g_balance_powerup_invincible_takedamage;
794                 }
795
796                 if (targ == attacker)
797                         damage = damage * autocvar_g_balance_selfdamagepercent; // Partial damage if the attacker hits himself
798
799                 // count the damage
800                 if(attacker)
801                 if(!IS_DEAD(targ))
802                 if(deathtype != DEATH_BUFF.m_id)
803                 if(targ.takedamage == DAMAGE_AIM)
804                 if(targ != attacker)
805                 {
806                         entity victim;
807                         if(IS_VEHICLE(targ) && targ.owner)
808                                 victim = targ.owner;
809                         else
810                                 victim = targ;
811
812                         if(IS_PLAYER(victim) || (IS_TURRET(victim) && victim.active == ACTIVE_ACTIVE) || IS_MONSTER(victim) || MUTATOR_CALLHOOK(PlayHitsound, victim, attacker))
813                         {
814                                 if(DIFF_TEAM(victim, attacker) && !STAT(FROZEN, victim))
815                                 {
816                                         if(damage > 0)
817                                         {
818                                                 if(deathtype != DEATH_FIRE.m_id)
819                                                 {
820                                                         if(PHYS_INPUT_BUTTON_CHAT(victim))
821                                                                 attacker.typehitsound += 1;
822                                                         else
823                                                                 attacker.damage_dealt += damage;
824                                                 }
825
826                                                 damage_goodhits += 1;
827                                                 damage_gooddamage += damage;
828
829                                                 if (!DEATH_ISSPECIAL(deathtype))
830                                                 {
831                                                         if(IS_PLAYER(targ)) // don't do this for vehicles
832                                                         if(IsFlying(victim))
833                                                                 yoda = 1;
834                                                 }
835                                         }
836                                 }
837                                 else
838                                 {
839                                         if(deathtype != DEATH_FIRE.m_id)
840                                         {
841                                                 attacker.typehitsound += 1;
842                                         }
843                                         if(complainteamdamage > 0)
844                                                 if(time > attacker.teamkill_complain)
845                                                 {
846                                                         attacker.teamkill_complain = time + 5;
847                                                         attacker.teamkill_soundtime = time + 0.4;
848                                                         attacker.teamkill_soundsource = targ;
849                                                 }
850                                 }
851                         }
852                 }
853         }
854
855         // apply push
856         if (targ.damageforcescale)
857         if (force)
858         if (!IS_PLAYER(targ) || time >= targ.spawnshieldtime || targ == attacker)
859         {
860                 vector farce = damage_explosion_calcpush(targ.damageforcescale * force, targ.velocity, autocvar_g_balance_damagepush_speedfactor);
861                 if(targ.move_movetype == MOVETYPE_PHYSICS)
862                 {
863                         entity farcent = new(farce);
864                         farcent.enemy = targ;
865                         farcent.movedir = farce * 10;
866                         if(targ.mass)
867                                 farcent.movedir = farcent.movedir * targ.mass;
868                         farcent.origin = hitloc;
869                         farcent.forcetype = FORCETYPE_FORCEATPOS;
870                         farcent.nextthink = time + 0.1;
871                         setthink(farcent, SUB_Remove);
872                 }
873                 else
874                 {
875                         targ.velocity = targ.velocity + farce;
876                 }
877                 UNSET_ONGROUND(targ);
878                 UpdateCSQCProjectile(targ);
879         }
880         // apply damage
881         if (damage != 0 || (targ.damageforcescale && force))
882         if (targ.event_damage)
883                 targ.event_damage (targ, inflictor, attacker, damage, deathtype, hitloc, force);
884
885         // apply mirror damage if any
886         if(!autocvar_g_mirrordamage_onlyweapons || DEATH_WEAPONOF(deathtype) != WEP_Null)
887         if(mirrordamage > 0 || mirrorforce > 0)
888         {
889                 attacker = attacker_save;
890
891                 force = normalize(attacker.origin + attacker.view_ofs - hitloc) * mirrorforce;
892                 Damage(attacker, inflictor, attacker, mirrordamage, DEATH_MIRRORDAMAGE.m_id, attacker.origin, force);
893         }
894 }
895
896 float RadiusDamageForSource (entity inflictor, vector inflictororigin, vector inflictorvelocity, entity attacker, float coredamage, float edgedamage, float rad, entity cantbe, entity mustbe, float inflictorselfdamage, float forceintensity, int deathtype, entity directhitentity)
897         // Returns total damage applies to creatures
898 {
899         entity  targ;
900         vector  force;
901         float   total_damage_to_creatures;
902         entity  next;
903         float   tfloordmg;
904         float   tfloorforce;
905
906         float stat_damagedone;
907
908         if(RadiusDamage_running)
909         {
910                 backtrace("RadiusDamage called recursively! Expect stuff to go HORRIBLY wrong.");
911                 return 0;
912         }
913
914         RadiusDamage_running = 1;
915
916         tfloordmg = autocvar_g_throughfloor_damage;
917         tfloorforce = autocvar_g_throughfloor_force;
918
919         total_damage_to_creatures = 0;
920
921         if(deathtype != (WEP_HOOK.m_id | HITTYPE_SECONDARY | HITTYPE_BOUNCE)) // only send gravity bomb damage once
922                 if(DEATH_WEAPONOF(deathtype) != WEP_TUBA) // do not send tuba damage (bandwidth hog)
923                 {
924                         force = inflictorvelocity;
925                         if(force == '0 0 0')
926                                 force = '0 0 -1';
927                         else
928                                 force = normalize(force);
929                         if(forceintensity >= 0)
930                                 Damage_DamageInfo(inflictororigin, coredamage, edgedamage, rad, forceintensity * force, deathtype, 0, attacker);
931                         else
932                                 Damage_DamageInfo(inflictororigin, coredamage, edgedamage, -rad, (-forceintensity) * force, deathtype, 0, attacker);
933                 }
934
935         stat_damagedone = 0;
936
937         targ = WarpZone_FindRadius (inflictororigin, rad + MAX_DAMAGEEXTRARADIUS, false);
938         while (targ)
939         {
940                 next = targ.chain;
941                 if ((targ != inflictor) || inflictorselfdamage)
942                 if (((cantbe != targ) && !mustbe) || (mustbe == targ))
943                 if (targ.takedamage)
944                 {
945                         vector nearest;
946                         vector diff;
947                         float power;
948
949                         // LordHavoc: measure distance to nearest point on target (not origin)
950                         // (this guarentees 100% damage on a touch impact)
951                         nearest = targ.WarpZone_findradius_nearest;
952                         diff = targ.WarpZone_findradius_dist;
953                         // round up a little on the damage to ensure full damage on impacts
954                         // and turn the distance into a fraction of the radius
955                         power = 1 - ((vlen (diff) - bound(MIN_DAMAGEEXTRARADIUS, targ.damageextraradius, MAX_DAMAGEEXTRARADIUS)) / rad);
956                         //bprint(" ");
957                         //bprint(ftos(power));
958                         //if (targ == attacker)
959                         //      print(ftos(power), "\n");
960                         if (power > 0)
961                         {
962                                 float finaldmg;
963                                 if (power > 1)
964                                         power = 1;
965                                 finaldmg = coredamage * power + edgedamage * (1 - power);
966                                 if (finaldmg > 0)
967                                 {
968                                         float a;
969                                         float c;
970                                         vector hitloc;
971                                         vector myblastorigin;
972                                         vector center;
973
974                                         myblastorigin = WarpZone_TransformOrigin(targ, inflictororigin);
975
976                                         // if it's a player, use the view origin as reference
977                                         center = CENTER_OR_VIEWOFS(targ);
978
979                                         force = normalize(center - myblastorigin);
980                                         force = force * (finaldmg / coredamage) * forceintensity;
981                                         hitloc = nearest;
982
983                                         if(deathtype & WEP_BLASTER.m_id)
984                                                 force *= WEP_CVAR_BOTH(blaster, !(deathtype & HITTYPE_SECONDARY), force_zscale);
985
986                                         if(targ != directhitentity)
987                                         {
988                                                 float hits;
989                                                 float total;
990                                                 float hitratio;
991                                                 float mininv_f, mininv_d;
992
993                                                 // test line of sight to multiple positions on box,
994                                                 // and do damage if any of them hit
995                                                 hits = 0;
996
997                                                 // we know: max stddev of hitratio = 1 / (2 * sqrt(n))
998                                                 // so for a given max stddev:
999                                                 // n = (1 / (2 * max stddev of hitratio))^2
1000
1001                                                 mininv_d = (finaldmg * (1-tfloordmg)) / autocvar_g_throughfloor_damage_max_stddev;
1002                                                 mininv_f = (vlen(force) * (1-tfloorforce)) / autocvar_g_throughfloor_force_max_stddev;
1003
1004                                                 if(autocvar_g_throughfloor_debug)
1005                                                         LOG_INFOF("THROUGHFLOOR: D=%f F=%f max(dD)=1/%f max(dF)=1/%f", finaldmg, vlen(force), mininv_d, mininv_f);
1006
1007
1008                                                 total = 0.25 * pow(max(mininv_f, mininv_d), 2);
1009
1010                                                 if(autocvar_g_throughfloor_debug)
1011                                                         LOG_INFOF(" steps=%f", total);
1012
1013
1014                                                 if (IS_PLAYER(targ))
1015                                                         total = ceil(bound(autocvar_g_throughfloor_min_steps_player, total, autocvar_g_throughfloor_max_steps_player));
1016                                                 else
1017                                                         total = ceil(bound(autocvar_g_throughfloor_min_steps_other, total, autocvar_g_throughfloor_max_steps_other));
1018
1019                                                 if(autocvar_g_throughfloor_debug)
1020                                                         LOG_INFOF(" steps=%f dD=%f dF=%f", total, finaldmg * (1-tfloordmg) / (2 * sqrt(total)), vlen(force) * (1-tfloorforce) / (2 * sqrt(total)));
1021
1022                                                 for(c = 0; c < total; ++c)
1023                                                 {
1024                                                         //traceline(targ.WarpZone_findradius_findorigin, nearest, MOVE_NOMONSTERS, inflictor);
1025                                                         WarpZone_TraceLine(inflictororigin, WarpZone_UnTransformOrigin(targ, nearest), MOVE_NOMONSTERS, inflictor);
1026                                                         if (trace_fraction == 1 || trace_ent == targ)
1027                                                         {
1028                                                                 ++hits;
1029                                                                 if (hits > 1)
1030                                                                         hitloc = hitloc + nearest;
1031                                                                 else
1032                                                                         hitloc = nearest;
1033                                                         }
1034                                                         nearest.x = targ.origin.x + targ.mins.x + random() * targ.size.x;
1035                                                         nearest.y = targ.origin.y + targ.mins.y + random() * targ.size.y;
1036                                                         nearest.z = targ.origin.z + targ.mins.z + random() * targ.size.z;
1037                                                 }
1038
1039                                                 nearest = hitloc * (1 / max(1, hits));
1040                                                 hitratio = (hits / total);
1041                                                 a = bound(0, tfloordmg + (1-tfloordmg) * hitratio, 1);
1042                                                 finaldmg = finaldmg * a;
1043                                                 a = bound(0, tfloorforce + (1-tfloorforce) * hitratio, 1);
1044                                                 force = force * a;
1045
1046                                                 if(autocvar_g_throughfloor_debug)
1047                                                         LOG_INFOF(" D=%f F=%f\n", finaldmg, vlen(force));
1048                                         }
1049
1050                                         //if (targ == attacker)
1051                                         //{
1052                                         //      print("hits ", ftos(hits), " / ", ftos(total));
1053                                         //      print(" finaldmg ", ftos(finaldmg), " force ", vtos(force));
1054                                         //      print(" (", ftos(a), ")\n");
1055                                         //}
1056                                         if(finaldmg || force)
1057                                         {
1058                                                 if(targ.iscreature)
1059                                                 {
1060                                                         total_damage_to_creatures += finaldmg;
1061
1062                                                         if(accuracy_isgooddamage(attacker, targ))
1063                                                                 stat_damagedone += finaldmg;
1064                                                 }
1065
1066                                                 if(targ == directhitentity || DEATH_ISSPECIAL(deathtype))
1067                                                         Damage (targ, inflictor, attacker, finaldmg, deathtype, nearest, force);
1068                                                 else
1069                                                         Damage (targ, inflictor, attacker, finaldmg, deathtype | HITTYPE_SPLASH, nearest, force);
1070                                         }
1071                                 }
1072                         }
1073                 }
1074                 targ = next;
1075         }
1076
1077         RadiusDamage_running = 0;
1078
1079         if(!DEATH_ISSPECIAL(deathtype))
1080                 accuracy_add(attacker, DEATH_WEAPONOF(deathtype).m_id, 0, min(coredamage, stat_damagedone));
1081
1082         return total_damage_to_creatures;
1083 }
1084
1085 float RadiusDamage (entity inflictor, entity attacker, float coredamage, float edgedamage, float rad, entity cantbe, entity mustbe, float forceintensity, int deathtype, entity directhitentity)
1086 {
1087         return RadiusDamageForSource (inflictor, (inflictor.origin + (inflictor.mins + inflictor.maxs) * 0.5), inflictor.velocity, attacker, coredamage, edgedamage, rad, cantbe, mustbe, false, forceintensity, deathtype, directhitentity);
1088 }
1089
1090 float Fire_IsBurning(entity e)
1091 {
1092         return (time < e.fire_endtime);
1093 }
1094
1095 float Fire_AddDamage(entity e, entity o, float d, float t, float dt)
1096 {
1097         float dps;
1098         float maxtime, mintime, maxdamage, mindamage, maxdps, mindps, totaldamage, totaltime;
1099
1100         if(IS_PLAYER(e))
1101         {
1102                 if(IS_DEAD(e))
1103                         return -1;
1104         }
1105         else
1106         {
1107                 if(!e.fire_burner)
1108                 {
1109                         // print("adding a fire burner to ", e.classname, "\n");
1110                         e.fire_burner = new(fireburner);
1111                         setthink(e.fire_burner, fireburner_think);
1112                         e.fire_burner.nextthink = time;
1113                         e.fire_burner.owner = e;
1114                 }
1115         }
1116
1117         t = max(t, 0.1);
1118         dps = d / t;
1119         if(Fire_IsBurning(e))
1120         {
1121                 mintime = e.fire_endtime - time;
1122                 maxtime = max(mintime, t);
1123
1124                 mindps = e.fire_damagepersec;
1125                 maxdps = max(mindps, dps);
1126
1127                 if(maxtime > mintime || maxdps > mindps)
1128                 {
1129                         // Constraints:
1130
1131                         // damage we have right now
1132                         mindamage = mindps * mintime;
1133
1134                         // damage we want to get
1135                         maxdamage = mindamage + d;
1136
1137                         // but we can't exceed maxtime * maxdps!
1138                         totaldamage = min(maxdamage, maxtime * maxdps);
1139
1140                         // LEMMA:
1141                         // Look at:
1142                         // totaldamage = min(mindamage + d, maxtime * maxdps)
1143                         // We see:
1144                         // totaldamage <= maxtime * maxdps
1145                         // ==> totaldamage / maxdps <= maxtime.
1146                         // We also see:
1147                         // totaldamage / mindps = min(mindamage / mindps + d, maxtime * maxdps / mindps)
1148                         //                     >= min(mintime, maxtime)
1149                         // ==> totaldamage / maxdps >= mintime.
1150
1151                         /*
1152                         // how long do we damage then?
1153                         // at least as long as before
1154                         // but, never exceed maxdps
1155                         totaltime = max(mintime, totaldamage / maxdps); // always <= maxtime due to lemma
1156                         */
1157
1158                         // alternate:
1159                         // at most as long as maximum allowed
1160                         // but, never below mindps
1161                         totaltime = min(maxtime, totaldamage / mindps); // always >= mintime due to lemma
1162
1163                         // assuming t > mintime, dps > mindps:
1164                         // we get d = t * dps = maxtime * maxdps
1165                         // totaldamage = min(maxdamage, maxtime * maxdps) = min(... + d, maxtime * maxdps) = maxtime * maxdps
1166                         // totaldamage / maxdps = maxtime
1167                         // totaldamage / mindps > totaldamage / maxdps = maxtime
1168                         // FROM THIS:
1169                         // a) totaltime = max(mintime, maxtime) = maxtime
1170                         // b) totaltime = min(maxtime, totaldamage / maxdps) = maxtime
1171
1172                         // assuming t <= mintime:
1173                         // we get maxtime = mintime
1174                         // a) totaltime = max(mintime, ...) >= mintime, also totaltime <= maxtime by the lemma, therefore totaltime = mintime = maxtime
1175                         // b) totaltime = min(maxtime, ...) <= maxtime, also totaltime >= mintime by the lemma, therefore totaltime = mintime = maxtime
1176
1177                         // assuming dps <= mindps:
1178                         // we get mindps = maxdps.
1179                         // With this, the lemma says that mintime <= totaldamage / mindps = totaldamage / maxdps <= maxtime.
1180                         // a) totaltime = max(mintime, totaldamage / maxdps) = totaldamage / maxdps
1181                         // b) totaltime = min(maxtime, totaldamage / mindps) = totaldamage / maxdps
1182
1183                         e.fire_damagepersec = totaldamage / totaltime;
1184                         e.fire_endtime = time + totaltime;
1185                         if(totaldamage > 1.2 * mindamage)
1186                         {
1187                                 e.fire_deathtype = dt;
1188                                 if(e.fire_owner != o)
1189                                 {
1190                                         e.fire_owner = o;
1191                                         e.fire_hitsound = false;
1192                                 }
1193                         }
1194                         if(accuracy_isgooddamage(o, e))
1195                                 accuracy_add(o, DEATH_WEAPONOF(dt).m_id, 0, max(0, totaldamage - mindamage));
1196                         return max(0, totaldamage - mindamage); // can never be negative, but to make sure
1197                 }
1198                 else
1199                         return 0;
1200         }
1201         else
1202         {
1203                 e.fire_damagepersec = dps;
1204                 e.fire_endtime = time + t;
1205                 e.fire_deathtype = dt;
1206                 e.fire_owner = o;
1207                 e.fire_hitsound = false;
1208                 if(accuracy_isgooddamage(o, e))
1209                         accuracy_add(o, DEATH_WEAPONOF(dt).m_id, 0, d);
1210                 return d;
1211         }
1212 }
1213
1214 void Fire_ApplyDamage(entity e)
1215 {
1216         float t, d, hi, ty;
1217         entity o;
1218
1219         if (!Fire_IsBurning(e))
1220                 return;
1221
1222         for(t = 0, o = e.owner; o.owner && t < 16; o = o.owner, ++t);
1223         if(IS_NOT_A_CLIENT(o))
1224                 o = e.fire_owner;
1225
1226         // water and slime stop fire
1227         if(e.waterlevel)
1228         if(e.watertype != CONTENT_LAVA)
1229                 e.fire_endtime = 0;
1230
1231         // ice stops fire
1232         if(STAT(FROZEN, e))
1233                 e.fire_endtime = 0;
1234
1235         t = min(frametime, e.fire_endtime - time);
1236         d = e.fire_damagepersec * t;
1237
1238         hi = e.fire_owner.damage_dealt;
1239         ty = e.fire_owner.typehitsound;
1240         Damage(e, e, e.fire_owner, d, e.fire_deathtype, e.origin, '0 0 0');
1241         if(e.fire_hitsound && e.fire_owner)
1242         {
1243                 e.fire_owner.damage_dealt = hi;
1244                 e.fire_owner.typehitsound = ty;
1245         }
1246         e.fire_hitsound = true;
1247
1248         if(!IS_INDEPENDENT_PLAYER(e))
1249         if(!STAT(FROZEN, e))
1250                 FOREACH_CLIENT(IS_PLAYER(it) && it != e, LAMBDA(
1251                         if(!IS_DEAD(it))
1252                         if(!IS_INDEPENDENT_PLAYER(it))
1253                         if(boxesoverlap(e.absmin, e.absmax, it.absmin, it.absmax))
1254                         {
1255                                 t = autocvar_g_balance_firetransfer_time * (e.fire_endtime - time);
1256                                 d = autocvar_g_balance_firetransfer_damage * e.fire_damagepersec * t;
1257                                 Fire_AddDamage(it, o, d, t, DEATH_FIRE.m_id);
1258                         }
1259                 ));
1260 }
1261
1262 void Fire_ApplyEffect(entity e)
1263 {
1264         if(Fire_IsBurning(e))
1265                 e.effects |= EF_FLAME;
1266         else
1267                 e.effects &= ~EF_FLAME;
1268 }
1269
1270 void fireburner_think(entity this)
1271 {
1272         // for players, this is done in the regular loop
1273         if(wasfreed(this.owner))
1274         {
1275                 delete(this);
1276                 return;
1277         }
1278         Fire_ApplyEffect(this.owner);
1279         if(!Fire_IsBurning(this.owner))
1280         {
1281                 this.owner.fire_burner = NULL;
1282                 delete(this);
1283                 return;
1284         }
1285         Fire_ApplyDamage(this.owner);
1286         this.nextthink = time;
1287 }