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