ef1e78802efbe4ca679530fb5cbda2198d4e4fb5
[xonotic/xonotic-data.pk3dir.git] / qcsrc / server / g_damage.qc
1 #include "g_damage.qh"
2
3 #include "bot/bot.qh"
4 #include "g_hook.qh"
5 #include "mutators/all.qh"
6 #include "scores.qh"
7 #include "spawnpoints.qh"
8 #include "../common/state.qh"
9 #include "../common/physics/player.qh"
10 #include "../common/t_items.qh"
11 #include "../common/vehicles/all.qh"
12 #include "../common/items/all.qc"
13 #include "../common/mutators/mutator/waypoints/waypointsprites.qh"
14 #include "weapons/accuracy.qh"
15 #include "weapons/csqcprojectile.qh"
16 #include "weapons/selection.qh"
17 #include "../common/constants.qh"
18 #include "../common/deathtypes/all.qh"
19 #include "../common/notifications/all.qh"
20 #include "../common/physics/movetypes/movetypes.qh"
21 #include "../common/playerstats.qh"
22 #include "../common/teams.qh"
23 #include "../common/util.qh"
24 #include "../common/weapons/all.qh"
25 #include "../lib/csqcmodel/sv_model.qh"
26 #include "../lib/warpzone/common.qh"
27
28 void UpdateFrags(entity player, float f)
29 {
30         PlayerTeamScore_AddScore(player, f);
31 }
32
33 void GiveFrags (entity attacker, entity targ, float f, int deathtype)
34 {
35         // TODO route through PlayerScores instead
36         if(gameover) return;
37
38         if(f < 0)
39         {
40                 if(targ == attacker)
41                 {
42                         // suicide
43                         PlayerScore_Add(attacker, SP_SUICIDES, 1);
44                 }
45                 else
46                 {
47                         // teamkill
48                         PlayerScore_Add(attacker, SP_KILLS, -1); // or maybe add a teamkills field?
49                 }
50         }
51         else
52         {
53                 // regular frag
54                 PlayerScore_Add(attacker, SP_KILLS, 1);
55                 if(targ.playerid)
56                         PS_GR_P_ADDVAL(attacker, sprintf("kills-%d", targ.playerid), 1);
57         }
58
59         PlayerScore_Add(targ, SP_DEATHS, 1);
60
61         if(targ != attacker) // not for suicides
62         if(g_weaponarena_random)
63         {
64                 // after a frag, exchange the current weapon (or the culprit, if detectable) by a new random weapon
65                 Weapon culprit = DEATH_WEAPONOF(deathtype);
66                 if(!culprit) culprit = PS(attacker).m_weapon;
67                 else if(!(attacker.weapons & (culprit.m_wepset))) culprit = PS(attacker).m_weapon;
68
69                 if(g_weaponarena_random_with_blaster && culprit == WEP_BLASTER) // WEAPONTODO: Shouldn't this be in a mutator?
70                 {
71                         // no exchange
72                 }
73                 else
74                 {
75                         if(!GiveFrags_randomweapons)
76                         {
77                                 GiveFrags_randomweapons = new(GiveFrags_randomweapons);
78                         }
79
80                         if(warmup_stage)
81                                 GiveFrags_randomweapons.weapons = WARMUP_START_WEAPONS;
82                         else
83                                 GiveFrags_randomweapons.weapons = start_weapons;
84
85                         // all others (including the culprit): remove
86                         GiveFrags_randomweapons.weapons &= ~attacker.weapons;
87                         GiveFrags_randomweapons.weapons &= ~(culprit.m_wepset);
88
89                         // among the remaining ones, choose one by random
90                         W_RandomWeapons(GiveFrags_randomweapons, 1);
91
92                         if(GiveFrags_randomweapons.weapons)
93                         {
94                                 attacker.weapons |= GiveFrags_randomweapons.weapons;
95                                 attacker.weapons &= ~(culprit.m_wepset);
96                         }
97                 }
98
99                 // after a frag, choose another random weapon set
100                 if (!(attacker.weapons & WepSet_FromWeapon(PS(attacker).m_weapon)))
101                         W_SwitchWeapon_Force(attacker, w_getbestweapon(attacker));
102         }
103
104         // FIXME fix the mess this is (we have REAL points now!)
105         if(MUTATOR_CALLHOOK(GiveFragsForKill, attacker, targ, f))
106                 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                 delete(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                 delete(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.move_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                 }
856                 UNSET_ONGROUND(targ);
857                 UpdateCSQCProjectile(targ);
858         }
859         // apply damage
860         if (damage != 0 || (targ.damageforcescale && vlen(force)))
861         if (targ.event_damage)
862                 targ.event_damage (targ, inflictor, attacker, damage, deathtype, hitloc, force);
863
864         // apply mirror damage if any
865         if(!autocvar_g_mirrordamage_onlyweapons || DEATH_WEAPONOF(deathtype) != WEP_Null)
866         if(mirrordamage > 0 || mirrorforce > 0)
867         {
868                 attacker = attacker_save;
869
870                 force = normalize(attacker.origin + attacker.view_ofs - hitloc) * mirrorforce;
871                 Damage(attacker, inflictor, attacker, mirrordamage, DEATH_MIRRORDAMAGE.m_id, attacker.origin, force);
872         }
873 }
874
875 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)
876         // Returns total damage applies to creatures
877 {
878         entity  targ;
879         vector  force;
880         float   total_damage_to_creatures;
881         entity  next;
882         float   tfloordmg;
883         float   tfloorforce;
884
885         float stat_damagedone;
886
887         if(RadiusDamage_running)
888         {
889                 backtrace("RadiusDamage called recursively! Expect stuff to go HORRIBLY wrong.");
890                 return 0;
891         }
892
893         RadiusDamage_running = 1;
894
895         tfloordmg = autocvar_g_throughfloor_damage;
896         tfloorforce = autocvar_g_throughfloor_force;
897
898         total_damage_to_creatures = 0;
899
900         if(deathtype != (WEP_HOOK.m_id | HITTYPE_SECONDARY | HITTYPE_BOUNCE)) // only send gravity bomb damage once
901                 if(DEATH_WEAPONOF(deathtype) != WEP_TUBA) // do not send tuba damage (bandwidth hog)
902                 {
903                         force = inflictorvelocity;
904                         if(force == '0 0 0')
905                                 force = '0 0 -1';
906                         else
907                                 force = normalize(force);
908                         if(forceintensity >= 0)
909                                 Damage_DamageInfo(inflictororigin, coredamage, edgedamage, rad, forceintensity * force, deathtype, 0, attacker);
910                         else
911                                 Damage_DamageInfo(inflictororigin, coredamage, edgedamage, -rad, (-forceintensity) * force, deathtype, 0, attacker);
912                 }
913
914         stat_damagedone = 0;
915
916         targ = WarpZone_FindRadius (inflictororigin, rad + MAX_DAMAGEEXTRARADIUS, false);
917         while (targ)
918         {
919                 next = targ.chain;
920                 if ((targ != inflictor) || inflictorselfdamage)
921                 if (((cantbe != targ) && !mustbe) || (mustbe == targ))
922                 if (targ.takedamage)
923                 {
924                         vector nearest;
925                         vector diff;
926                         float power;
927
928                         // LordHavoc: measure distance to nearest point on target (not origin)
929                         // (this guarentees 100% damage on a touch impact)
930                         nearest = targ.WarpZone_findradius_nearest;
931                         diff = targ.WarpZone_findradius_dist;
932                         // round up a little on the damage to ensure full damage on impacts
933                         // and turn the distance into a fraction of the radius
934                         power = 1 - ((vlen (diff) - bound(MIN_DAMAGEEXTRARADIUS, targ.damageextraradius, MAX_DAMAGEEXTRARADIUS)) / rad);
935                         //bprint(" ");
936                         //bprint(ftos(power));
937                         //if (targ == attacker)
938                         //      print(ftos(power), "\n");
939                         if (power > 0)
940                         {
941                                 float finaldmg;
942                                 if (power > 1)
943                                         power = 1;
944                                 finaldmg = coredamage * power + edgedamage * (1 - power);
945                                 if (finaldmg > 0)
946                                 {
947                                         float a;
948                                         float c;
949                                         vector hitloc;
950                                         vector myblastorigin;
951                                         vector center;
952
953                                         myblastorigin = WarpZone_TransformOrigin(targ, inflictororigin);
954
955                                         // if it's a player, use the view origin as reference
956                                         center = CENTER_OR_VIEWOFS(targ);
957
958                                         force = normalize(center - myblastorigin);
959                                         force = force * (finaldmg / coredamage) * forceintensity;
960                                         hitloc = nearest;
961
962                                         if(deathtype & WEP_BLASTER.m_id)
963                                                 force *= WEP_CVAR_BOTH(blaster, !(deathtype & HITTYPE_SECONDARY), force_zscale);
964
965                                         if(targ != directhitentity)
966                                         {
967                                                 float hits;
968                                                 float total;
969                                                 float hitratio;
970                                                 float mininv_f, mininv_d;
971
972                                                 // test line of sight to multiple positions on box,
973                                                 // and do damage if any of them hit
974                                                 hits = 0;
975
976                                                 // we know: max stddev of hitratio = 1 / (2 * sqrt(n))
977                                                 // so for a given max stddev:
978                                                 // n = (1 / (2 * max stddev of hitratio))^2
979
980                                                 mininv_d = (finaldmg * (1-tfloordmg)) / autocvar_g_throughfloor_damage_max_stddev;
981                                                 mininv_f = (vlen(force) * (1-tfloorforce)) / autocvar_g_throughfloor_force_max_stddev;
982
983                                                 if(autocvar_g_throughfloor_debug)
984                                                         LOG_INFOF("THROUGHFLOOR: D=%f F=%f max(dD)=1/%f max(dF)=1/%f", finaldmg, vlen(force), mininv_d, mininv_f);
985
986
987                                                 total = 0.25 * pow(max(mininv_f, mininv_d), 2);
988
989                                                 if(autocvar_g_throughfloor_debug)
990                                                         LOG_INFOF(" steps=%f", total);
991
992
993                                                 if (IS_PLAYER(targ))
994                                                         total = ceil(bound(autocvar_g_throughfloor_min_steps_player, total, autocvar_g_throughfloor_max_steps_player));
995                                                 else
996                                                         total = ceil(bound(autocvar_g_throughfloor_min_steps_other, total, autocvar_g_throughfloor_max_steps_other));
997
998                                                 if(autocvar_g_throughfloor_debug)
999                                                         LOG_INFOF(" steps=%f dD=%f dF=%f", total, finaldmg * (1-tfloordmg) / (2 * sqrt(total)), vlen(force) * (1-tfloorforce) / (2 * sqrt(total)));
1000
1001                                                 for(c = 0; c < total; ++c)
1002                                                 {
1003                                                         //traceline(targ.WarpZone_findradius_findorigin, nearest, MOVE_NOMONSTERS, inflictor);
1004                                                         WarpZone_TraceLine(inflictororigin, WarpZone_UnTransformOrigin(targ, nearest), MOVE_NOMONSTERS, inflictor);
1005                                                         if (trace_fraction == 1 || trace_ent == targ)
1006                                                         {
1007                                                                 ++hits;
1008                                                                 if (hits > 1)
1009                                                                         hitloc = hitloc + nearest;
1010                                                                 else
1011                                                                         hitloc = nearest;
1012                                                         }
1013                                                         nearest.x = targ.origin.x + targ.mins.x + random() * targ.size.x;
1014                                                         nearest.y = targ.origin.y + targ.mins.y + random() * targ.size.y;
1015                                                         nearest.z = targ.origin.z + targ.mins.z + random() * targ.size.z;
1016                                                 }
1017
1018                                                 nearest = hitloc * (1 / max(1, hits));
1019                                                 hitratio = (hits / total);
1020                                                 a = bound(0, tfloordmg + (1-tfloordmg) * hitratio, 1);
1021                                                 finaldmg = finaldmg * a;
1022                                                 a = bound(0, tfloorforce + (1-tfloorforce) * hitratio, 1);
1023                                                 force = force * a;
1024
1025                                                 if(autocvar_g_throughfloor_debug)
1026                                                         LOG_INFOF(" D=%f F=%f\n", finaldmg, vlen(force));
1027                                         }
1028
1029                                         //if (targ == attacker)
1030                                         //{
1031                                         //      print("hits ", ftos(hits), " / ", ftos(total));
1032                                         //      print(" finaldmg ", ftos(finaldmg), " force ", vtos(force));
1033                                         //      print(" (", ftos(a), ")\n");
1034                                         //}
1035                                         if(finaldmg || vlen(force))
1036                                         {
1037                                                 if(targ.iscreature)
1038                                                 {
1039                                                         total_damage_to_creatures += finaldmg;
1040
1041                                                         if(accuracy_isgooddamage(attacker, targ))
1042                                                                 stat_damagedone += finaldmg;
1043                                                 }
1044
1045                                                 if(targ == directhitentity || DEATH_ISSPECIAL(deathtype))
1046                                                         Damage (targ, inflictor, attacker, finaldmg, deathtype, nearest, force);
1047                                                 else
1048                                                         Damage (targ, inflictor, attacker, finaldmg, deathtype | HITTYPE_SPLASH, nearest, force);
1049                                         }
1050                                 }
1051                         }
1052                 }
1053                 targ = next;
1054         }
1055
1056         RadiusDamage_running = 0;
1057
1058         if(!DEATH_ISSPECIAL(deathtype))
1059                 accuracy_add(attacker, DEATH_WEAPONOF(deathtype).m_id, 0, min(coredamage, stat_damagedone));
1060
1061         return total_damage_to_creatures;
1062 }
1063
1064 float RadiusDamage (entity inflictor, entity attacker, float coredamage, float edgedamage, float rad, entity cantbe, entity mustbe, float forceintensity, int deathtype, entity directhitentity)
1065 {
1066         return RadiusDamageForSource (inflictor, (inflictor.origin + (inflictor.mins + inflictor.maxs) * 0.5), inflictor.velocity, attacker, coredamage, edgedamage, rad, cantbe, mustbe, false, forceintensity, deathtype, directhitentity);
1067 }
1068
1069 float Fire_IsBurning(entity e)
1070 {
1071         return (time < e.fire_endtime);
1072 }
1073
1074 float Fire_AddDamage(entity e, entity o, float d, float t, float dt)
1075 {
1076         float dps;
1077         float maxtime, mintime, maxdamage, mindamage, maxdps, mindps, totaldamage, totaltime;
1078
1079         if(IS_PLAYER(e))
1080         {
1081                 if(IS_DEAD(e))
1082                         return -1;
1083         }
1084         else
1085         {
1086                 if(!e.fire_burner)
1087                 {
1088                         // print("adding a fire burner to ", e.classname, "\n");
1089                         e.fire_burner = new(fireburner);
1090                         setthink(e.fire_burner, fireburner_think);
1091                         e.fire_burner.nextthink = time;
1092                         e.fire_burner.owner = e;
1093                 }
1094         }
1095
1096         t = max(t, 0.1);
1097         dps = d / t;
1098         if(Fire_IsBurning(e))
1099         {
1100                 mintime = e.fire_endtime - time;
1101                 maxtime = max(mintime, t);
1102
1103                 mindps = e.fire_damagepersec;
1104                 maxdps = max(mindps, dps);
1105
1106                 if(maxtime > mintime || maxdps > mindps)
1107                 {
1108                         // Constraints:
1109
1110                         // damage we have right now
1111                         mindamage = mindps * mintime;
1112
1113                         // damage we want to get
1114                         maxdamage = mindamage + d;
1115
1116                         // but we can't exceed maxtime * maxdps!
1117                         totaldamage = min(maxdamage, maxtime * maxdps);
1118
1119                         // LEMMA:
1120                         // Look at:
1121                         // totaldamage = min(mindamage + d, maxtime * maxdps)
1122                         // We see:
1123                         // totaldamage <= maxtime * maxdps
1124                         // ==> totaldamage / maxdps <= maxtime.
1125                         // We also see:
1126                         // totaldamage / mindps = min(mindamage / mindps + d, maxtime * maxdps / mindps)
1127                         //                     >= min(mintime, maxtime)
1128                         // ==> totaldamage / maxdps >= mintime.
1129
1130                         /*
1131                         // how long do we damage then?
1132                         // at least as long as before
1133                         // but, never exceed maxdps
1134                         totaltime = max(mintime, totaldamage / maxdps); // always <= maxtime due to lemma
1135                         */
1136
1137                         // alternate:
1138                         // at most as long as maximum allowed
1139                         // but, never below mindps
1140                         totaltime = min(maxtime, totaldamage / mindps); // always >= mintime due to lemma
1141
1142                         // assuming t > mintime, dps > mindps:
1143                         // we get d = t * dps = maxtime * maxdps
1144                         // totaldamage = min(maxdamage, maxtime * maxdps) = min(... + d, maxtime * maxdps) = maxtime * maxdps
1145                         // totaldamage / maxdps = maxtime
1146                         // totaldamage / mindps > totaldamage / maxdps = maxtime
1147                         // FROM THIS:
1148                         // a) totaltime = max(mintime, maxtime) = maxtime
1149                         // b) totaltime = min(maxtime, totaldamage / maxdps) = maxtime
1150
1151                         // assuming t <= mintime:
1152                         // we get maxtime = mintime
1153                         // a) totaltime = max(mintime, ...) >= mintime, also totaltime <= maxtime by the lemma, therefore totaltime = mintime = maxtime
1154                         // b) totaltime = min(maxtime, ...) <= maxtime, also totaltime >= mintime by the lemma, therefore totaltime = mintime = maxtime
1155
1156                         // assuming dps <= mindps:
1157                         // we get mindps = maxdps.
1158                         // With this, the lemma says that mintime <= totaldamage / mindps = totaldamage / maxdps <= maxtime.
1159                         // a) totaltime = max(mintime, totaldamage / maxdps) = totaldamage / maxdps
1160                         // b) totaltime = min(maxtime, totaldamage / mindps) = totaldamage / maxdps
1161
1162                         e.fire_damagepersec = totaldamage / totaltime;
1163                         e.fire_endtime = time + totaltime;
1164                         if(totaldamage > 1.2 * mindamage)
1165                         {
1166                                 e.fire_deathtype = dt;
1167                                 if(e.fire_owner != o)
1168                                 {
1169                                         e.fire_owner = o;
1170                                         e.fire_hitsound = false;
1171                                 }
1172                         }
1173                         if(accuracy_isgooddamage(o, e))
1174                                 accuracy_add(o, DEATH_WEAPONOF(dt).m_id, 0, max(0, totaldamage - mindamage));
1175                         return max(0, totaldamage - mindamage); // can never be negative, but to make sure
1176                 }
1177                 else
1178                         return 0;
1179         }
1180         else
1181         {
1182                 e.fire_damagepersec = dps;
1183                 e.fire_endtime = time + t;
1184                 e.fire_deathtype = dt;
1185                 e.fire_owner = o;
1186                 e.fire_hitsound = false;
1187                 if(accuracy_isgooddamage(o, e))
1188                         accuracy_add(o, DEATH_WEAPONOF(dt).m_id, 0, d);
1189                 return d;
1190         }
1191 }
1192
1193 void Fire_ApplyDamage(entity e)
1194 {
1195         float t, d, hi, ty;
1196         entity o;
1197
1198         if (!Fire_IsBurning(e))
1199                 return;
1200
1201         for(t = 0, o = e.owner; o.owner && t < 16; o = o.owner, ++t);
1202         if(IS_NOT_A_CLIENT(o))
1203                 o = e.fire_owner;
1204
1205         // water and slime stop fire
1206         if(e.waterlevel)
1207         if(e.watertype != CONTENT_LAVA)
1208                 e.fire_endtime = 0;
1209
1210         // ice stops fire
1211         if(STAT(FROZEN, e))
1212                 e.fire_endtime = 0;
1213
1214         t = min(frametime, e.fire_endtime - time);
1215         d = e.fire_damagepersec * t;
1216
1217         hi = e.fire_owner.damage_dealt;
1218         ty = e.fire_owner.typehitsound;
1219         Damage(e, e, e.fire_owner, d, e.fire_deathtype, e.origin, '0 0 0');
1220         if(e.fire_hitsound && e.fire_owner)
1221         {
1222                 e.fire_owner.damage_dealt = hi;
1223                 e.fire_owner.typehitsound = ty;
1224         }
1225         e.fire_hitsound = true;
1226
1227         if(!IS_INDEPENDENT_PLAYER(e))
1228         if(!STAT(FROZEN, e))
1229                 FOREACH_CLIENT(IS_PLAYER(it) && it != e, LAMBDA(
1230                         if(!IS_DEAD(it))
1231                         if(!IS_INDEPENDENT_PLAYER(it))
1232                         if(boxesoverlap(e.absmin, e.absmax, it.absmin, it.absmax))
1233                         {
1234                                 t = autocvar_g_balance_firetransfer_time * (e.fire_endtime - time);
1235                                 d = autocvar_g_balance_firetransfer_damage * e.fire_damagepersec * t;
1236                                 Fire_AddDamage(it, o, d, t, DEATH_FIRE.m_id);
1237                         }
1238                 ));
1239 }
1240
1241 void Fire_ApplyEffect(entity e)
1242 {
1243         if(Fire_IsBurning(e))
1244                 e.effects |= EF_FLAME;
1245         else
1246                 e.effects &= ~EF_FLAME;
1247 }
1248
1249 void fireburner_think(entity this)
1250 {
1251         // for players, this is done in the regular loop
1252         if(wasfreed(this.owner))
1253         {
1254                 delete(this);
1255                 return;
1256         }
1257         Fire_ApplyEffect(this.owner);
1258         if(!Fire_IsBurning(this.owner))
1259         {
1260                 this.owner.fire_burner = NULL;
1261                 delete(this);
1262                 return;
1263         }
1264         Fire_ApplyDamage(this.owner);
1265         this.nextthink = time;
1266 }