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