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