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