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