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