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