]> de.git.xonotic.org Git - xonotic/xonotic-data.pk3dir.git/blob - qcsrc/server/g_damage.qc
Fix bad code indentation
[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                 targ.health = ((IS_PLAYER(targ)) ? start_health : targ.max_health);
576
577         STAT(FROZEN, targ) = 0;
578         targ.revive_progress = 0;
579         targ.revival_time = time;
580         if(!targ.bot_attack)
581                 IL_PUSH(g_bot_targets, targ);
582         targ.bot_attack = true;
583
584         WaypointSprite_Kill(targ.waypointsprite_attached);
585
586         FOREACH_CLIENT(IS_PLAYER(it) && it.hook.aiment == targ, LAMBDA(RemoveGrapplingHook(it)));
587
588         // remove the ice block
589         if(targ.iceblock)
590                 delete(targ.iceblock);
591         targ.iceblock = NULL;
592 }
593
594 void Damage (entity targ, entity inflictor, entity attacker, float damage, int deathtype, vector hitloc, vector force)
595 {
596         float mirrordamage;
597         float mirrorforce;
598         float complainteamdamage = 0;
599         entity attacker_save;
600         mirrordamage = 0;
601         mirrorforce = 0;
602
603         if (gameover || targ.killcount == FRAGS_SPECTATOR)
604                 return;
605
606     damage_targ = targ;
607     damage_inflictor = inflictor;
608     damage_attacker = attacker;
609         attacker_save = attacker;
610
611         if(IS_PLAYER(targ))
612                 if(targ.hook)
613                         if(targ.hook.aiment)
614                                 if(targ.hook.aiment == attacker)
615                                         RemoveGrapplingHook(targ); // STOP THAT, you parasite!
616
617         // special rule: gravity bomb does not hit team mates (other than for disconnecting the hook)
618         if(DEATH_ISWEAPON(deathtype, WEP_HOOK) || DEATH_ISWEAPON(deathtype, WEP_TUBA))
619         {
620                 if(IS_PLAYER(targ) && SAME_TEAM(targ, attacker))
621                 {
622                         return;
623                 }
624         }
625
626         if(deathtype == DEATH_KILL.m_id || deathtype == DEATH_TEAMCHANGE.m_id || deathtype == DEATH_AUTOTEAMCHANGE.m_id)
627         {
628                 // exit the vehicle before killing (fixes a crash)
629                 if(IS_PLAYER(targ) && targ.vehicle)
630                         vehicles_exit(targ.vehicle, VHEF_RELEASE);
631
632                 // These are ALWAYS lethal
633                 // No damage modification here
634                 // Instead, prepare the victim for his death...
635                 targ.armorvalue = 0;
636                 targ.spawnshieldtime = 0;
637                 targ.health = 0.9; // this is < 1
638                 targ.flags -= targ.flags & FL_GODMODE;
639                 damage = 100000;
640         }
641         else if(deathtype == DEATH_MIRRORDAMAGE.m_id || deathtype == DEATH_NOAMMO.m_id)
642         {
643                 // no processing
644         }
645         else
646         {
647                 // nullify damage if teamplay is on
648                 if(deathtype != DEATH_TELEFRAG.m_id)
649                 if(IS_PLAYER(attacker))
650                 {
651                         if(IS_PLAYER(targ) && targ != attacker && (IS_INDEPENDENT_PLAYER(attacker) || IS_INDEPENDENT_PLAYER(targ)))
652                         {
653                                 damage = 0;
654                                 force = '0 0 0';
655                         }
656                         else if(SAME_TEAM(attacker, targ))
657                         {
658                                 if(autocvar_teamplay_mode == 1)
659                                         damage = 0;
660                                 else if(attacker != targ)
661                                 {
662                                         if(autocvar_teamplay_mode == 3)
663                                                 damage = 0;
664                                         else if(autocvar_teamplay_mode == 4)
665                                         {
666                                                 if(IS_PLAYER(targ) && !IS_DEAD(targ))
667                                                 {
668                                                         attacker.dmg_team = attacker.dmg_team + damage;
669                                                         complainteamdamage = attacker.dmg_team - autocvar_g_teamdamage_threshold;
670                                                         if(complainteamdamage > 0)
671                                                                 mirrordamage = autocvar_g_mirrordamage * complainteamdamage;
672                                                         mirrorforce = autocvar_g_mirrordamage * vlen(force);
673                                                         damage = autocvar_g_friendlyfire * damage;
674                                                         // mirrordamage will be used LATER
675
676                                                         if(autocvar_g_mirrordamage_virtual)
677                                                         {
678                                                                 vector v  = healtharmor_applydamage(attacker.armorvalue, autocvar_g_balance_armor_blockpercent, deathtype, mirrordamage);
679                                                                 attacker.dmg_take += v.x;
680                                                                 attacker.dmg_save += v.y;
681                                                                 attacker.dmg_inflictor = inflictor;
682                                                                 mirrordamage = v.z;
683                                                                 mirrorforce = 0;
684                                                         }
685
686                                                         if(autocvar_g_friendlyfire_virtual)
687                                                         {
688                                                                 vector v = healtharmor_applydamage(targ.armorvalue, autocvar_g_balance_armor_blockpercent, deathtype, damage);
689                                                                 targ.dmg_take += v.x;
690                                                                 targ.dmg_save += v.y;
691                                                                 targ.dmg_inflictor = inflictor;
692                                                                 damage = 0;
693                                                                 if(!autocvar_g_friendlyfire_virtual_force)
694                                                                         force = '0 0 0';
695                                                         }
696                                                 }
697                                                 else
698                                                         damage = 0;
699                                         }
700                                 }
701                         }
702                 }
703
704                 if (!DEATH_ISSPECIAL(deathtype))
705                 {
706                         damage *= g_weapondamagefactor;
707                         mirrordamage *= g_weapondamagefactor;
708                         complainteamdamage *= g_weapondamagefactor;
709                         force = force * g_weaponforcefactor;
710                         mirrorforce *= g_weaponforcefactor;
711                 }
712
713                 // should this be changed at all? If so, in what way?
714                 MUTATOR_CALLHOOK(PlayerDamage_Calculate, inflictor, attacker, targ, deathtype, damage, mirrordamage, force);
715                 damage = M_ARGV(4, float);
716                 mirrordamage = M_ARGV(5, float);
717                 force = M_ARGV(6, vector);
718
719                 if(STAT(FROZEN, targ))
720                 if(deathtype != DEATH_HURTTRIGGER.m_id && deathtype != DEATH_TEAMCHANGE.m_id && deathtype != DEATH_AUTOTEAMCHANGE.m_id)
721                 {
722                         if(autocvar_g_frozen_revive_falldamage > 0)
723                         if(deathtype == DEATH_FALL.m_id)
724                         if(damage >= autocvar_g_frozen_revive_falldamage)
725                         {
726                                 Unfreeze(targ);
727                                 targ.health = autocvar_g_frozen_revive_falldamage_health;
728                                 Send_Effect(EFFECT_ICEORGLASS, targ.origin, '0 0 0', 3);
729                                 Send_Notification(NOTIF_ALL, NULL, MSG_INFO, INFO_FREEZETAG_REVIVED_FALL, targ.netname);
730                                 Send_Notification(NOTIF_ONE, targ, MSG_CENTER, CENTER_FREEZETAG_REVIVE_SELF);
731                         }
732
733                         damage = 0;
734                         force *= autocvar_g_frozen_force;
735                 }
736
737                 if(STAT(FROZEN, targ) && deathtype == DEATH_HURTTRIGGER.m_id && !autocvar_g_frozen_damage_trigger)
738                 {
739                         Send_Effect(EFFECT_TELEPORT, targ.origin, '0 0 0', 1);
740
741                         entity spot = SelectSpawnPoint (targ, false);
742
743                         if(spot)
744                         {
745                                 damage = 0;
746                                 targ.deadflag = DEAD_NO;
747
748                                 targ.angles = spot.angles;
749
750                                 targ.effects = 0;
751                                 targ.effects |= EF_TELEPORT_BIT;
752
753                                 targ.angles_z = 0; // never spawn tilted even if the spot says to
754                                 targ.fixangle = true; // turn this way immediately
755                                 targ.velocity = '0 0 0';
756                                 targ.avelocity = '0 0 0';
757                                 targ.punchangle = '0 0 0';
758                                 targ.punchvector = '0 0 0';
759                                 targ.oldvelocity = targ.velocity;
760
761                                 targ.spawnorigin = spot.origin;
762                                 setorigin(targ, spot.origin + '0 0 1' * (1 - targ.mins.z - 24));
763                                 // don't reset back to last position, even if new position is stuck in solid
764                                 targ.oldorigin = targ.origin;
765                                 targ.prevorigin = targ.origin;
766
767                                 Send_Effect(EFFECT_TELEPORT, targ.origin, '0 0 0', 1);
768                         }
769                 }
770
771                 if(!g_instagib)
772                 {
773                         // apply strength multiplier
774                         if (attacker.items & ITEM_Strength.m_itemid)
775                         {
776                                 if(targ == attacker)
777                                 {
778                                         damage = damage * autocvar_g_balance_powerup_strength_selfdamage;
779                                         force = force * autocvar_g_balance_powerup_strength_selfforce;
780                                 }
781                                 else
782                                 {
783                                         damage = damage * autocvar_g_balance_powerup_strength_damage;
784                                         force = force * autocvar_g_balance_powerup_strength_force;
785                                 }
786                         }
787
788                         // apply invincibility multiplier
789                         if (targ.items & ITEM_Shield.m_itemid)
790                                 damage = damage * autocvar_g_balance_powerup_invincible_takedamage;
791                 }
792
793                 if (targ == attacker)
794                         damage = damage * autocvar_g_balance_selfdamagepercent; // Partial damage if the attacker hits himself
795
796                 // count the damage
797                 if(attacker)
798                 if(!IS_DEAD(targ))
799                 if(deathtype != DEATH_BUFF.m_id)
800                 if(targ.takedamage == DAMAGE_AIM)
801                 if(targ != attacker)
802                 {
803                         entity victim;
804                         if(IS_VEHICLE(targ) && targ.owner)
805                                 victim = targ.owner;
806                         else
807                                 victim = targ;
808
809                         if(IS_PLAYER(victim) || (IS_TURRET(victim) && victim.active == ACTIVE_ACTIVE) || IS_MONSTER(victim) || MUTATOR_CALLHOOK(PlayHitsound, victim, attacker))
810                         {
811                                 if(DIFF_TEAM(victim, attacker) && !STAT(FROZEN, victim))
812                                 {
813                                         if(damage > 0)
814                                         {
815                                                 if(deathtype != DEATH_FIRE.m_id)
816                                                 {
817                                                         if(PHYS_INPUT_BUTTON_CHAT(victim))
818                                                                 attacker.typehitsound += 1;
819                                                         else
820                                                                 attacker.damage_dealt += damage;
821                                                 }
822
823                                                 damage_goodhits += 1;
824                                                 damage_gooddamage += damage;
825
826                                                 if (!DEATH_ISSPECIAL(deathtype))
827                                                 {
828                                                         if(IS_PLAYER(targ)) // don't do this for vehicles
829                                                         if(IsFlying(victim))
830                                                                 yoda = 1;
831                                                 }
832                                         }
833                                 }
834                                 else
835                                 {
836                                         if(deathtype != DEATH_FIRE.m_id)
837                                         {
838                                                 attacker.typehitsound += 1;
839                                         }
840                                         if(complainteamdamage > 0)
841                                                 if(time > attacker.teamkill_complain)
842                                                 {
843                                                         attacker.teamkill_complain = time + 5;
844                                                         attacker.teamkill_soundtime = time + 0.4;
845                                                         attacker.teamkill_soundsource = targ;
846                                                 }
847                                 }
848                         }
849                 }
850         }
851
852         // apply push
853         if (targ.damageforcescale)
854         if (force)
855         if (!IS_PLAYER(targ) || time >= targ.spawnshieldtime || targ == attacker)
856         {
857                 vector farce = damage_explosion_calcpush(targ.damageforcescale * force, targ.velocity, autocvar_g_balance_damagepush_speedfactor);
858                 if(targ.move_movetype == MOVETYPE_PHYSICS)
859                 {
860                         entity farcent = new(farce);
861                         farcent.enemy = targ;
862                         farcent.movedir = farce * 10;
863                         if(targ.mass)
864                                 farcent.movedir = farcent.movedir * targ.mass;
865                         farcent.origin = hitloc;
866                         farcent.forcetype = FORCETYPE_FORCEATPOS;
867                         farcent.nextthink = time + 0.1;
868                         setthink(farcent, SUB_Remove);
869                 }
870                 else
871                 {
872                         targ.velocity = targ.velocity + farce;
873                 }
874                 UNSET_ONGROUND(targ);
875                 UpdateCSQCProjectile(targ);
876         }
877         // apply damage
878         if (damage != 0 || (targ.damageforcescale && force))
879         if (targ.event_damage)
880                 targ.event_damage (targ, inflictor, attacker, damage, deathtype, hitloc, force);
881
882         // apply mirror damage if any
883         if(!autocvar_g_mirrordamage_onlyweapons || DEATH_WEAPONOF(deathtype) != WEP_Null)
884         if(mirrordamage > 0 || mirrorforce > 0)
885         {
886                 attacker = attacker_save;
887
888                 force = normalize(attacker.origin + attacker.view_ofs - hitloc) * mirrorforce;
889                 Damage(attacker, inflictor, attacker, mirrordamage, DEATH_MIRRORDAMAGE.m_id, attacker.origin, force);
890         }
891 }
892
893 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)
894         // Returns total damage applies to creatures
895 {
896         entity  targ;
897         vector  force;
898         float   total_damage_to_creatures;
899         entity  next;
900         float   tfloordmg;
901         float   tfloorforce;
902
903         float stat_damagedone;
904
905         if(RadiusDamage_running)
906         {
907                 backtrace("RadiusDamage called recursively! Expect stuff to go HORRIBLY wrong.");
908                 return 0;
909         }
910
911         RadiusDamage_running = 1;
912
913         tfloordmg = autocvar_g_throughfloor_damage;
914         tfloorforce = autocvar_g_throughfloor_force;
915
916         total_damage_to_creatures = 0;
917
918         if(deathtype != (WEP_HOOK.m_id | HITTYPE_SECONDARY | HITTYPE_BOUNCE)) // only send gravity bomb damage once
919                 if(DEATH_WEAPONOF(deathtype) != WEP_TUBA) // do not send tuba damage (bandwidth hog)
920                 {
921                         force = inflictorvelocity;
922                         if(force == '0 0 0')
923                                 force = '0 0 -1';
924                         else
925                                 force = normalize(force);
926                         if(forceintensity >= 0)
927                                 Damage_DamageInfo(inflictororigin, coredamage, edgedamage, rad, forceintensity * force, deathtype, 0, attacker);
928                         else
929                                 Damage_DamageInfo(inflictororigin, coredamage, edgedamage, -rad, (-forceintensity) * force, deathtype, 0, attacker);
930                 }
931
932         stat_damagedone = 0;
933
934         targ = WarpZone_FindRadius (inflictororigin, rad + MAX_DAMAGEEXTRARADIUS, false);
935         while (targ)
936         {
937                 next = targ.chain;
938                 if ((targ != inflictor) || inflictorselfdamage)
939                 if (((cantbe != targ) && !mustbe) || (mustbe == targ))
940                 if (targ.takedamage)
941                 {
942                         vector nearest;
943                         vector diff;
944                         float power;
945
946                         // LordHavoc: measure distance to nearest point on target (not origin)
947                         // (this guarentees 100% damage on a touch impact)
948                         nearest = targ.WarpZone_findradius_nearest;
949                         diff = targ.WarpZone_findradius_dist;
950                         // round up a little on the damage to ensure full damage on impacts
951                         // and turn the distance into a fraction of the radius
952                         power = 1 - ((vlen (diff) - bound(MIN_DAMAGEEXTRARADIUS, targ.damageextraradius, MAX_DAMAGEEXTRARADIUS)) / rad);
953                         //bprint(" ");
954                         //bprint(ftos(power));
955                         //if (targ == attacker)
956                         //      print(ftos(power), "\n");
957                         if (power > 0)
958                         {
959                                 float finaldmg;
960                                 if (power > 1)
961                                         power = 1;
962                                 finaldmg = coredamage * power + edgedamage * (1 - power);
963                                 if (finaldmg > 0)
964                                 {
965                                         float a;
966                                         float c;
967                                         vector hitloc;
968                                         vector myblastorigin;
969                                         vector center;
970
971                                         myblastorigin = WarpZone_TransformOrigin(targ, inflictororigin);
972
973                                         // if it's a player, use the view origin as reference
974                                         center = CENTER_OR_VIEWOFS(targ);
975
976                                         force = normalize(center - myblastorigin);
977                                         force = force * (finaldmg / coredamage) * forceintensity;
978                                         hitloc = nearest;
979
980                                         if(deathtype & WEP_BLASTER.m_id)
981                                                 force *= WEP_CVAR_BOTH(blaster, !(deathtype & HITTYPE_SECONDARY), force_zscale);
982
983                                         if(targ != directhitentity)
984                                         {
985                                                 float hits;
986                                                 float total;
987                                                 float hitratio;
988                                                 float mininv_f, mininv_d;
989
990                                                 // test line of sight to multiple positions on box,
991                                                 // and do damage if any of them hit
992                                                 hits = 0;
993
994                                                 // we know: max stddev of hitratio = 1 / (2 * sqrt(n))
995                                                 // so for a given max stddev:
996                                                 // n = (1 / (2 * max stddev of hitratio))^2
997
998                                                 mininv_d = (finaldmg * (1-tfloordmg)) / autocvar_g_throughfloor_damage_max_stddev;
999                                                 mininv_f = (vlen(force) * (1-tfloorforce)) / autocvar_g_throughfloor_force_max_stddev;
1000
1001                                                 if(autocvar_g_throughfloor_debug)
1002                                                         LOG_INFOF("THROUGHFLOOR: D=%f F=%f max(dD)=1/%f max(dF)=1/%f", finaldmg, vlen(force), mininv_d, mininv_f);
1003
1004
1005                                                 total = 0.25 * pow(max(mininv_f, mininv_d), 2);
1006
1007                                                 if(autocvar_g_throughfloor_debug)
1008                                                         LOG_INFOF(" steps=%f", total);
1009
1010
1011                                                 if (IS_PLAYER(targ))
1012                                                         total = ceil(bound(autocvar_g_throughfloor_min_steps_player, total, autocvar_g_throughfloor_max_steps_player));
1013                                                 else
1014                                                         total = ceil(bound(autocvar_g_throughfloor_min_steps_other, total, autocvar_g_throughfloor_max_steps_other));
1015
1016                                                 if(autocvar_g_throughfloor_debug)
1017                                                         LOG_INFOF(" steps=%f dD=%f dF=%f", total, finaldmg * (1-tfloordmg) / (2 * sqrt(total)), vlen(force) * (1-tfloorforce) / (2 * sqrt(total)));
1018
1019                                                 for(c = 0; c < total; ++c)
1020                                                 {
1021                                                         //traceline(targ.WarpZone_findradius_findorigin, nearest, MOVE_NOMONSTERS, inflictor);
1022                                                         WarpZone_TraceLine(inflictororigin, WarpZone_UnTransformOrigin(targ, nearest), MOVE_NOMONSTERS, inflictor);
1023                                                         if (trace_fraction == 1 || trace_ent == targ)
1024                                                         {
1025                                                                 ++hits;
1026                                                                 if (hits > 1)
1027                                                                         hitloc = hitloc + nearest;
1028                                                                 else
1029                                                                         hitloc = nearest;
1030                                                         }
1031                                                         nearest.x = targ.origin.x + targ.mins.x + random() * targ.size.x;
1032                                                         nearest.y = targ.origin.y + targ.mins.y + random() * targ.size.y;
1033                                                         nearest.z = targ.origin.z + targ.mins.z + random() * targ.size.z;
1034                                                 }
1035
1036                                                 nearest = hitloc * (1 / max(1, hits));
1037                                                 hitratio = (hits / total);
1038                                                 a = bound(0, tfloordmg + (1-tfloordmg) * hitratio, 1);
1039                                                 finaldmg = finaldmg * a;
1040                                                 a = bound(0, tfloorforce + (1-tfloorforce) * hitratio, 1);
1041                                                 force = force * a;
1042
1043                                                 if(autocvar_g_throughfloor_debug)
1044                                                         LOG_INFOF(" D=%f F=%f\n", finaldmg, vlen(force));
1045                                         }
1046
1047                                         //if (targ == attacker)
1048                                         //{
1049                                         //      print("hits ", ftos(hits), " / ", ftos(total));
1050                                         //      print(" finaldmg ", ftos(finaldmg), " force ", vtos(force));
1051                                         //      print(" (", ftos(a), ")\n");
1052                                         //}
1053                                         if(finaldmg || force)
1054                                         {
1055                                                 if(targ.iscreature)
1056                                                 {
1057                                                         total_damage_to_creatures += finaldmg;
1058
1059                                                         if(accuracy_isgooddamage(attacker, targ))
1060                                                                 stat_damagedone += finaldmg;
1061                                                 }
1062
1063                                                 if(targ == directhitentity || DEATH_ISSPECIAL(deathtype))
1064                                                         Damage (targ, inflictor, attacker, finaldmg, deathtype, nearest, force);
1065                                                 else
1066                                                         Damage (targ, inflictor, attacker, finaldmg, deathtype | HITTYPE_SPLASH, nearest, force);
1067                                         }
1068                                 }
1069                         }
1070                 }
1071                 targ = next;
1072         }
1073
1074         RadiusDamage_running = 0;
1075
1076         if(!DEATH_ISSPECIAL(deathtype))
1077                 accuracy_add(attacker, DEATH_WEAPONOF(deathtype).m_id, 0, min(coredamage, stat_damagedone));
1078
1079         return total_damage_to_creatures;
1080 }
1081
1082 float RadiusDamage (entity inflictor, entity attacker, float coredamage, float edgedamage, float rad, entity cantbe, entity mustbe, float forceintensity, int deathtype, entity directhitentity)
1083 {
1084         return RadiusDamageForSource (inflictor, (inflictor.origin + (inflictor.mins + inflictor.maxs) * 0.5), inflictor.velocity, attacker, coredamage, edgedamage, rad, cantbe, mustbe, false, forceintensity, deathtype, directhitentity);
1085 }
1086
1087 float Fire_IsBurning(entity e)
1088 {
1089         return (time < e.fire_endtime);
1090 }
1091
1092 float Fire_AddDamage(entity e, entity o, float d, float t, float dt)
1093 {
1094         float dps;
1095         float maxtime, mintime, maxdamage, mindamage, maxdps, mindps, totaldamage, totaltime;
1096
1097         if(IS_PLAYER(e))
1098         {
1099                 if(IS_DEAD(e))
1100                         return -1;
1101         }
1102         else
1103         {
1104                 if(!e.fire_burner)
1105                 {
1106                         // print("adding a fire burner to ", e.classname, "\n");
1107                         e.fire_burner = new(fireburner);
1108                         setthink(e.fire_burner, fireburner_think);
1109                         e.fire_burner.nextthink = time;
1110                         e.fire_burner.owner = e;
1111                 }
1112         }
1113
1114         t = max(t, 0.1);
1115         dps = d / t;
1116         if(Fire_IsBurning(e))
1117         {
1118                 mintime = e.fire_endtime - time;
1119                 maxtime = max(mintime, t);
1120
1121                 mindps = e.fire_damagepersec;
1122                 maxdps = max(mindps, dps);
1123
1124                 if(maxtime > mintime || maxdps > mindps)
1125                 {
1126                         // Constraints:
1127
1128                         // damage we have right now
1129                         mindamage = mindps * mintime;
1130
1131                         // damage we want to get
1132                         maxdamage = mindamage + d;
1133
1134                         // but we can't exceed maxtime * maxdps!
1135                         totaldamage = min(maxdamage, maxtime * maxdps);
1136
1137                         // LEMMA:
1138                         // Look at:
1139                         // totaldamage = min(mindamage + d, maxtime * maxdps)
1140                         // We see:
1141                         // totaldamage <= maxtime * maxdps
1142                         // ==> totaldamage / maxdps <= maxtime.
1143                         // We also see:
1144                         // totaldamage / mindps = min(mindamage / mindps + d, maxtime * maxdps / mindps)
1145                         //                     >= min(mintime, maxtime)
1146                         // ==> totaldamage / maxdps >= mintime.
1147
1148                         /*
1149                         // how long do we damage then?
1150                         // at least as long as before
1151                         // but, never exceed maxdps
1152                         totaltime = max(mintime, totaldamage / maxdps); // always <= maxtime due to lemma
1153                         */
1154
1155                         // alternate:
1156                         // at most as long as maximum allowed
1157                         // but, never below mindps
1158                         totaltime = min(maxtime, totaldamage / mindps); // always >= mintime due to lemma
1159
1160                         // assuming t > mintime, dps > mindps:
1161                         // we get d = t * dps = maxtime * maxdps
1162                         // totaldamage = min(maxdamage, maxtime * maxdps) = min(... + d, maxtime * maxdps) = maxtime * maxdps
1163                         // totaldamage / maxdps = maxtime
1164                         // totaldamage / mindps > totaldamage / maxdps = maxtime
1165                         // FROM THIS:
1166                         // a) totaltime = max(mintime, maxtime) = maxtime
1167                         // b) totaltime = min(maxtime, totaldamage / maxdps) = maxtime
1168
1169                         // assuming t <= mintime:
1170                         // we get maxtime = mintime
1171                         // a) totaltime = max(mintime, ...) >= mintime, also totaltime <= maxtime by the lemma, therefore totaltime = mintime = maxtime
1172                         // b) totaltime = min(maxtime, ...) <= maxtime, also totaltime >= mintime by the lemma, therefore totaltime = mintime = maxtime
1173
1174                         // assuming dps <= mindps:
1175                         // we get mindps = maxdps.
1176                         // With this, the lemma says that mintime <= totaldamage / mindps = totaldamage / maxdps <= maxtime.
1177                         // a) totaltime = max(mintime, totaldamage / maxdps) = totaldamage / maxdps
1178                         // b) totaltime = min(maxtime, totaldamage / mindps) = totaldamage / maxdps
1179
1180                         e.fire_damagepersec = totaldamage / totaltime;
1181                         e.fire_endtime = time + totaltime;
1182                         if(totaldamage > 1.2 * mindamage)
1183                         {
1184                                 e.fire_deathtype = dt;
1185                                 if(e.fire_owner != o)
1186                                 {
1187                                         e.fire_owner = o;
1188                                         e.fire_hitsound = false;
1189                                 }
1190                         }
1191                         if(accuracy_isgooddamage(o, e))
1192                                 accuracy_add(o, DEATH_WEAPONOF(dt).m_id, 0, max(0, totaldamage - mindamage));
1193                         return max(0, totaldamage - mindamage); // can never be negative, but to make sure
1194                 }
1195                 else
1196                         return 0;
1197         }
1198         else
1199         {
1200                 e.fire_damagepersec = dps;
1201                 e.fire_endtime = time + t;
1202                 e.fire_deathtype = dt;
1203                 e.fire_owner = o;
1204                 e.fire_hitsound = false;
1205                 if(accuracy_isgooddamage(o, e))
1206                         accuracy_add(o, DEATH_WEAPONOF(dt).m_id, 0, d);
1207                 return d;
1208         }
1209 }
1210
1211 void Fire_ApplyDamage(entity e)
1212 {
1213         float t, d, hi, ty;
1214         entity o;
1215
1216         if (!Fire_IsBurning(e))
1217                 return;
1218
1219         for(t = 0, o = e.owner; o.owner && t < 16; o = o.owner, ++t);
1220         if(IS_NOT_A_CLIENT(o))
1221                 o = e.fire_owner;
1222
1223         // water and slime stop fire
1224         if(e.waterlevel)
1225         if(e.watertype != CONTENT_LAVA)
1226                 e.fire_endtime = 0;
1227
1228         // ice stops fire
1229         if(STAT(FROZEN, e))
1230                 e.fire_endtime = 0;
1231
1232         t = min(frametime, e.fire_endtime - time);
1233         d = e.fire_damagepersec * t;
1234
1235         hi = e.fire_owner.damage_dealt;
1236         ty = e.fire_owner.typehitsound;
1237         Damage(e, e, e.fire_owner, d, e.fire_deathtype, e.origin, '0 0 0');
1238         if(e.fire_hitsound && e.fire_owner)
1239         {
1240                 e.fire_owner.damage_dealt = hi;
1241                 e.fire_owner.typehitsound = ty;
1242         }
1243         e.fire_hitsound = true;
1244
1245         if(!IS_INDEPENDENT_PLAYER(e))
1246         if(!STAT(FROZEN, e))
1247                 FOREACH_CLIENT(IS_PLAYER(it) && it != e, LAMBDA(
1248                         if(!IS_DEAD(it))
1249                         if(!IS_INDEPENDENT_PLAYER(it))
1250                         if(boxesoverlap(e.absmin, e.absmax, it.absmin, it.absmax))
1251                         {
1252                                 t = autocvar_g_balance_firetransfer_time * (e.fire_endtime - time);
1253                                 d = autocvar_g_balance_firetransfer_damage * e.fire_damagepersec * t;
1254                                 Fire_AddDamage(it, o, d, t, DEATH_FIRE.m_id);
1255                         }
1256                 ));
1257 }
1258
1259 void Fire_ApplyEffect(entity e)
1260 {
1261         if(Fire_IsBurning(e))
1262                 e.effects |= EF_FLAME;
1263         else
1264                 e.effects &= ~EF_FLAME;
1265 }
1266
1267 void fireburner_think(entity this)
1268 {
1269         // for players, this is done in the regular loop
1270         if(wasfreed(this.owner))
1271         {
1272                 delete(this);
1273                 return;
1274         }
1275         Fire_ApplyEffect(this.owner);
1276         if(!Fire_IsBurning(this.owner))
1277         {
1278                 this.owner.fire_burner = NULL;
1279                 delete(this);
1280                 return;
1281         }
1282         Fire_ApplyDamage(this.owner);
1283         this.nextthink = time;
1284 }