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