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