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