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