]> de.git.xonotic.org Git - xonotic/xonotic-data.pk3dir.git/blob - qcsrc/server/g_damage.qc
A few trillion lines of untested junk, committing now so it doesn't become quadrillions
[xonotic/xonotic-data.pk3dir.git] / qcsrc / server / g_damage.qc
1 #include "g_damage.qh"
2
3 #if defined(CSQC)
4 #elif defined(MENUQC)
5 #elif defined(SVQC)
6     #include "../warpzonelib/common.qh"
7     #include "../common/constants.qh"
8     #include "../common/teams.qh"
9     #include "../common/util.qh"
10     #include "../common/weapons/weapons.qh"
11     #include "weapons/accuracy.qh"
12     #include "weapons/csqcprojectile.qh"
13     #include "weapons/selection.qh"
14     #include "t_items.qh"
15     #include "autocvars.qh"
16     #include "constants.qh"
17     #include "defs.qh"
18     #include "../common/notifications.qh"
19     #include "../common/deathtypes.qh"
20     #include "mutators/mutators_include.qh"
21     #include "tturrets/include/turrets_early.qh"
22     #include "vehicles/vehicles_def.qh"
23     #include "../csqcmodellib/sv_model.qh"
24     #include "../common/playerstats.qh"
25     #include "g_hook.qh"
26     #include "scores.qh"
27     #include "spawnpoints.qh"
28 #endif
29
30 float Damage_DamageInfo_SendEntity(entity to, float sf)
31 {
32         WriteByte(MSG_ENTITY, ENT_CLIENT_DAMAGEINFO);
33         WriteShort(MSG_ENTITY, self.projectiledeathtype);
34         WriteCoord(MSG_ENTITY, floor(self.origin.x));
35         WriteCoord(MSG_ENTITY, floor(self.origin.y));
36         WriteCoord(MSG_ENTITY, floor(self.origin.z));
37         WriteByte(MSG_ENTITY, bound(1, self.dmg, 255));
38         WriteByte(MSG_ENTITY, bound(0, self.dmg_radius, 255));
39         WriteByte(MSG_ENTITY, bound(1, self.dmg_edge, 255));
40         WriteShort(MSG_ENTITY, self.oldorigin.x);
41         WriteByte(MSG_ENTITY, self.species);
42         return true;
43 }
44
45 void Damage_DamageInfo(vector org, float coredamage, float edgedamage, float rad, vector force, float deathtype, float bloodtype, entity dmgowner)
46 {
47         // TODO maybe call this from non-edgedamage too?
48         // TODO maybe make the client do the particle effects for the weapons and the impact sounds using this info?
49
50         entity e;
51
52         if(!sound_allowed(MSG_BROADCAST, dmgowner))
53                 deathtype |= 0x8000;
54
55         e = spawn();
56         setorigin(e, org);
57         e.projectiledeathtype = deathtype;
58         e.dmg = coredamage;
59         e.dmg_edge = edgedamage;
60         e.dmg_radius = rad;
61         e.dmg_force = vlen(force);
62         e.velocity = force;
63         e.oldorigin_x = compressShortVector(e.velocity);
64         e.species = bloodtype;
65
66         Net_LinkEntity(e, false, 0.2, Damage_DamageInfo_SendEntity);
67 }
68
69 void UpdateFrags(entity player, float f)
70 {
71         PlayerTeamScore_AddScore(player, f);
72 }
73
74 void GiveFrags (entity attacker, entity targ, float f, float deathtype)
75 {
76         // TODO route through PlayerScores instead
77         if(gameover) return;
78
79         if(f < 0)
80         {
81                 if(targ == attacker)
82                 {
83                         // suicide
84                         PlayerScore_Add(attacker, SP_SUICIDES, 1);
85                 }
86                 else
87                 {
88                         // teamkill
89                         PlayerScore_Add(attacker, SP_KILLS, -1); // or maybe add a teamkills field?
90                 }
91         }
92         else
93         {
94                 // regular frag
95                 PlayerScore_Add(attacker, SP_KILLS, 1);
96                 if(targ.playerid)
97                         PS_GR_P_ADDVAL(attacker, sprintf("kills-%d", targ.playerid), 1);
98         }
99
100         PlayerScore_Add(targ, SP_DEATHS, 1);
101
102         if(targ != attacker) // not for suicides
103         if(g_weaponarena_random)
104         {
105                 // after a frag, exchange the current weapon (or the culprit, if detectable) by a new random weapon
106                 float culprit;
107                 culprit = DEATH_WEAPONOF(deathtype);
108                 if(!culprit)
109                         culprit = attacker.weapon;
110                 else if(!(attacker.weapons & WepSet_FromWeapon(culprit)))
111                         culprit = attacker.weapon;
112
113                 if(g_weaponarena_random_with_blaster && culprit == WEP_BLASTER) // WEAPONTODO: Shouldn't this be in a mutator?
114                 {
115                         // no exchange
116                 }
117                 else
118                 {
119                         if(!GiveFrags_randomweapons)
120                         {
121                                 GiveFrags_randomweapons = spawn();
122                                 GiveFrags_randomweapons.classname = "GiveFrags_randomweapons";
123                         }
124
125                         if(warmup_stage)
126                                 GiveFrags_randomweapons.weapons = WARMUP_START_WEAPONS;
127                         else
128                                 GiveFrags_randomweapons.weapons = start_weapons;
129
130                         // all others (including the culprit): remove
131                         GiveFrags_randomweapons.weapons &= ~attacker.weapons;
132                         GiveFrags_randomweapons.weapons &= ~WepSet_FromWeapon(culprit);
133
134                         // among the remaining ones, choose one by random
135                         W_RandomWeapons(GiveFrags_randomweapons, 1);
136
137                         if(GiveFrags_randomweapons.weapons)
138                         {
139                                 attacker.weapons |= GiveFrags_randomweapons.weapons;
140                                 attacker.weapons &= ~WepSet_FromWeapon(culprit);
141                         }
142                 }
143
144                 // after a frag, choose another random weapon set
145                 if (!(attacker.weapons & WepSet_FromWeapon(attacker.weapon)))
146                         W_SwitchWeapon_Force(attacker, w_getbestweapon(attacker));
147         }
148
149         // FIXME fix the mess this is (we have REAL points now!)
150         entity oldself;
151         oldself = self;
152         self = attacker;
153         frag_attacker = attacker;
154         frag_target = targ;
155         frag_score = f;
156         if(MUTATOR_CALLHOOK(GiveFragsForKill))
157         {
158                 f = frag_score;
159                 self = oldself;
160         }
161         else
162         {
163                 self = oldself;
164         }
165
166         attacker.totalfrags += f;
167
168         if(f)
169                 UpdateFrags(attacker, f);
170 }
171
172 string AppendItemcodes(string s, entity player)
173 {
174         float w;
175         w = player.weapon;
176         //if(w == 0)
177         //      w = player.switchweapon;
178         if(w == 0)
179                 w = player.cnt; // previous weapon!
180         s = strcat(s, ftos(w));
181         if(time < player.strength_finished)
182                 s = strcat(s, "S");
183         if(time < player.invincible_finished)
184                 s = strcat(s, "I");
185         if(player.flagcarried != world)
186                 s = strcat(s, "F");
187         if(player.BUTTON_CHAT)
188                 s = strcat(s, "T");
189         if(player.kh_next)
190                 s = strcat(s, "K");
191         return s;
192 }
193
194 void LogDeath(string mode, float deathtype, entity killer, entity killed)
195 {
196         string s;
197         if(!autocvar_sv_eventlog)
198                 return;
199         s = strcat(":kill:", mode);
200         s = strcat(s, ":", ftos(killer.playerid));
201         s = strcat(s, ":", ftos(killed.playerid));
202         s = strcat(s, ":type=", Deathtype_Name(deathtype));
203         s = strcat(s, ":items=");
204         s = AppendItemcodes(s, killer);
205         if(killed != killer)
206         {
207                 s = strcat(s, ":victimitems=");
208                 s = AppendItemcodes(s, killed);
209         }
210         GameLogEcho(s);
211 }
212
213 void Obituary_SpecialDeath(
214         entity notif_target,
215         float murder,
216         float deathtype,
217         string s1, string s2, string s3,
218         float f1, float f2, float f3)
219 {
220         if(DEATH_ISSPECIAL(deathtype))
221         {
222                 entity deathent = deathtypes[(deathtype - DT_FIRST)];
223                 if (!deathent) { backtrace("Obituary_SpecialDeath: Could not find deathtype entity!\n"); return; }
224
225                 if(murder)
226                 {
227                         if(deathent.death_msgmurder)
228                         {
229                                 Send_Notification_WOCOVA(
230                                         NOTIF_ONE,
231                                         notif_target,
232                                         MSG_MULTI,
233                                         deathent.death_msgmurder.nent_id,
234                                         s1, s2, s3, "",
235                                         f1, f2, f3, 0
236                                 );
237                                 Send_Notification_WOCOVA(
238                                         NOTIF_ALL_EXCEPT,
239                                         notif_target,
240                                         MSG_INFO,
241                                         deathent.death_msgmurder.nent_msginfo.nent_id,
242                                         s1, s2, s3, "",
243                                         f1, f2, f3, 0
244                                 );
245                         }
246                 }
247                 else
248                 {
249                         if(deathent.death_msgself)
250                         {
251                                 Send_Notification_WOCOVA(
252                                         NOTIF_ONE,
253                                         notif_target,
254                                         MSG_MULTI,
255                                         deathent.death_msgself.nent_id,
256                                         s1, s2, s3, "",
257                                         f1, f2, f3, 0
258                                 );
259                                 Send_Notification_WOCOVA(
260                                         NOTIF_ALL_EXCEPT,
261                                         notif_target,
262                                         MSG_INFO,
263                                         deathent.death_msgself.nent_msginfo.nent_id,
264                                         s1, s2, s3, "",
265                                         f1, f2, f3, 0
266                                 );
267                         }
268                 }
269         }
270         else { backtrace("Obituary_SpecialDeath called without a special deathtype?\n"); return; }
271 }
272
273 float Obituary_WeaponDeath(
274         entity notif_target,
275         float murder,
276         float deathtype,
277         string s1, string s2, string s3,
278         float f1, float f2)
279 {
280         float death_weapon = DEATH_WEAPONOF(deathtype);
281         if(death_weapon)
282         {
283                 w_deathtype = deathtype;
284                 float death_message = WEP_ACTION(death_weapon, ((murder) ? WR_KILLMESSAGE : WR_SUICIDEMESSAGE));
285                 w_deathtype = false;
286
287                 if(death_message)
288                 {
289                         Send_Notification_WOCOVA(
290                                 NOTIF_ONE,
291                                 notif_target,
292                                 MSG_MULTI,
293                                 death_message,
294                                 s1, s2, s3, "",
295                                 f1, f2, 0, 0
296                         );
297                         Send_Notification_WOCOVA(
298                                 NOTIF_ALL_EXCEPT,
299                                 notif_target,
300                                 MSG_INFO,
301                                 msg_multi_notifs[death_message - 1].nent_msginfo.nent_id,
302                                 s1, s2, s3, "",
303                                 f1, f2, 0, 0
304                         );
305                 }
306                 else
307                 {
308                         dprintf(
309                                 "Obituary_WeaponDeath(): ^1Deathtype ^7(%d)^1 has no notification for weapon %d!\n",
310                                 deathtype,
311                                 death_weapon
312                         );
313                 }
314
315                 return true;
316         }
317         return false;
318 }
319
320 void Obituary(entity attacker, entity inflictor, entity targ, float deathtype)
321 {
322         // Sanity check
323         if (!IS_PLAYER(targ)) { backtrace("Obituary called on non-player?!\n"); return; }
324
325         // Declarations
326         float notif_firstblood = false;
327         float kill_count_to_attacker, kill_count_to_target;
328
329         // Set final information for the death
330         targ.death_origin = targ.origin;
331         if(targ != attacker) { targ.killer_origin = attacker.origin; }
332         string deathlocation = (autocvar_notification_server_allows_location ? NearestLocation(targ.death_origin) : "");
333
334         #ifdef NOTIFICATIONS_DEBUG
335         Debug_Notification(
336                 sprintf(
337                         "Obituary(%s, %s, %s, %s = %d);\n",
338                         attacker.netname,
339                         inflictor.netname,
340                         targ.netname,
341                         Deathtype_Name(deathtype),
342                         deathtype
343                 )
344         );
345         #endif
346
347         // =======
348         // SUICIDE
349         // =======
350         if(targ == attacker)
351         {
352                 if(DEATH_ISSPECIAL(deathtype))
353                 {
354                         if(deathtype == DEATH_TEAMCHANGE || deathtype == DEATH_AUTOTEAMCHANGE)
355                         {
356                                 Obituary_SpecialDeath(targ, false, deathtype, targ.netname, deathlocation, "", targ.team, 0, 0);
357                         }
358                         else
359                         {
360                                 switch(deathtype)
361                                 {
362                                         case DEATH_MIRRORDAMAGE:
363                                         {
364                                                 Obituary_SpecialDeath(targ, false, deathtype, targ.netname, deathlocation, "", targ.killcount, 0, 0);
365                                                 break;
366                                         }
367
368                                         default:
369                                         {
370                                                 Obituary_SpecialDeath(targ, false, deathtype, targ.netname, deathlocation, "", targ.killcount, 0, 0);
371                                                 break;
372                                         }
373                                 }
374                         }
375                 }
376                 else if (!Obituary_WeaponDeath(targ, false, deathtype, targ.netname, deathlocation, "", targ.killcount, 0))
377                 {
378                         backtrace("SUICIDE: what the hell happened here?\n");
379                         return;
380                 }
381                 LogDeath("suicide", deathtype, targ, targ);
382                 GiveFrags(attacker, targ, -1, deathtype);
383         }
384
385         // ======
386         // MURDER
387         // ======
388         else if(IS_PLAYER(attacker))
389         {
390                 if(SAME_TEAM(attacker, targ))
391                 {
392                         LogDeath("tk", deathtype, attacker, targ);
393                         GiveFrags(attacker, targ, -1, deathtype);
394
395                         attacker.killcount = 0;
396
397                         Send_Notification(NOTIF_ONE, attacker, MSG_CENTER, CENTER_DEATH_TEAMKILL_FRAG, targ.netname);
398                         Send_Notification(NOTIF_ONE, targ, MSG_CENTER, CENTER_DEATH_TEAMKILL_FRAGGED, attacker.netname);
399                         Send_Notification(NOTIF_ALL, world, MSG_INFO, APP_TEAM_NUM_4(targ.team, INFO_DEATH_TEAMKILL_), targ.netname, attacker.netname, deathlocation, targ.killcount);
400
401                         // In this case, the death message will ALWAYS be "foo was betrayed by bar"
402                         // No need for specific death/weapon messages...
403                 }
404                 else
405                 {
406                         LogDeath("frag", deathtype, attacker, targ);
407                         GiveFrags(attacker, targ, 1, deathtype);
408
409                         attacker.taunt_soundtime = time + 1;
410                         attacker.killcount = attacker.killcount + 1;
411
412                         #define SPREE_ITEM(counta,countb,center,normal,gentle) \
413                                 case counta: \
414                                 { \
415                                         Send_Notification(NOTIF_ONE, attacker, MSG_ANNCE, ANNCE_KILLSTREAK_##countb); \
416                                         PS_GR_P_ADDVAL(attacker, PLAYERSTATS_ACHIEVEMENT_KILL_SPREE_##counta, 1); \
417                                         break; \
418                                 }
419                         switch(attacker.killcount)
420                         {
421                                 KILL_SPREE_LIST
422                                 default: break;
423                         }
424                         #undef SPREE_ITEM
425
426                         if(!checkrules_firstblood)
427                         {
428                                 checkrules_firstblood = true;
429                                 notif_firstblood = true; // modify the current messages so that they too show firstblood information
430                                 PS_GR_P_ADDVAL(attacker, PLAYERSTATS_ACHIEVEMENT_FIRSTBLOOD, 1);
431                                 PS_GR_P_ADDVAL(targ, PLAYERSTATS_ACHIEVEMENT_FIRSTVICTIM, 1);
432
433                                 // tell spree_inf and spree_cen that this is a first-blood and first-victim event
434                                 kill_count_to_attacker = -1;
435                                 kill_count_to_target = -2;
436                         }
437                         else
438                         {
439                                 kill_count_to_attacker = attacker.killcount;
440                                 kill_count_to_target = 0;
441                         }
442
443                         if(targ.istypefrag)
444                         {
445                                 Send_Notification(
446                                         NOTIF_ONE,
447                                         attacker,
448                                         MSG_CHOICE,
449                                         CHOICE_TYPEFRAG,
450                                         targ.netname,
451                                         kill_count_to_attacker,
452                                         (IS_BOT_CLIENT(targ) ? NO_MSG : targ.ping)
453                                 );
454                                 Send_Notification(
455                                         NOTIF_ONE,
456                                         targ,
457                                         MSG_CHOICE,
458                                         CHOICE_TYPEFRAGGED,
459                                         attacker.netname,
460                                         kill_count_to_target,
461                                         attacker.health,
462                                         attacker.armorvalue,
463                                         (IS_BOT_CLIENT(attacker) ? NO_MSG : attacker.ping)
464                                 );
465                         }
466                         else
467                         {
468                                 Send_Notification(
469                                         NOTIF_ONE,
470                                         attacker,
471                                         MSG_CHOICE,
472                                         CHOICE_FRAG,
473                                         targ.netname,
474                                         kill_count_to_attacker,
475                                         (IS_BOT_CLIENT(targ) ? NO_MSG : targ.ping)
476                                 );
477                                 Send_Notification(
478                                         NOTIF_ONE,
479                                         targ,
480                                         MSG_CHOICE,
481                                         CHOICE_FRAGGED,
482                                         attacker.netname,
483                                         kill_count_to_target,
484                                         attacker.health,
485                                         attacker.armorvalue,
486                                         (IS_BOT_CLIENT(attacker) ? NO_MSG : attacker.ping)
487                                 );
488                         }
489
490                         if (!Obituary_WeaponDeath(targ, true, deathtype, targ.netname, attacker.netname, deathlocation, targ.killcount, kill_count_to_attacker))
491                                 Obituary_SpecialDeath(targ, true, deathtype, targ.netname, attacker.netname, deathlocation, targ.killcount, kill_count_to_attacker, 0);
492                 }
493         }
494
495         // =============
496         // ACCIDENT/TRAP
497         // =============
498         else
499         {
500                 switch(deathtype)
501                 {
502                         // For now, we're just forcing HURTTRIGGER to behave as "DEATH_VOID" and giving it no special options...
503                         // Later on you will only be able to make custom messages using DEATH_CUSTOM,
504                         // and there will be a REAL DEATH_VOID implementation which mappers will use.
505                         /*case DEATH_HURTTRIGGER:
506                         {
507                                 s1 = targ.netname;
508                                 s2 = inflictor.message;
509                                 if(strstrofs(s2, "%", 0) < 0) { s2 = strcat("%s ", s2); }
510                                 break;
511                         }*/
512
513                         case DEATH_CUSTOM:
514                         {
515                                 Obituary_SpecialDeath(targ, false, deathtype,
516                                         targ.netname,
517                                         ((strstrofs(deathmessage, "%", 0) < 0) ? strcat("%s ", deathmessage) : deathmessage),
518                                         deathlocation,
519                                         targ.killcount,
520                                         0,
521                                         0);
522                                 break;
523                         }
524
525                         default:
526                         {
527                                 Obituary_SpecialDeath(targ, false, deathtype, targ.netname, deathlocation, "", targ.killcount, 0, 0);
528                                 break;
529                         }
530                 }
531
532                 LogDeath("accident", deathtype, targ, targ);
533                 GiveFrags(targ, targ, -1, deathtype);
534
535                 if(PlayerScore_Add(targ, SP_SCORE, 0) == -5)
536                 {
537                         Send_Notification(NOTIF_ONE, targ, MSG_ANNCE, ANNCE_ACHIEVEMENT_BOTLIKE);
538                         PS_GR_P_ADDVAL(attacker, PLAYERSTATS_ACHIEVEMENT_BOTLIKE, 1);
539                 }
540         }
541
542         // reset target kill count
543         if(targ.killcount) { targ.killcount = 0; }
544 }
545
546 void Ice_Think()
547 {
548         if(!self.owner.frozen || self.owner.iceblock != self)
549         {
550                 remove(self);
551                 return;
552         }
553         setorigin(self, self.owner.origin - '0 0 16');
554         self.nextthink = time;
555 }
556
557 void Freeze (entity targ, float freeze_time, float frozen_type, float show_waypoint)
558 {
559         if(!IS_PLAYER(targ) && !(targ.flags & FL_MONSTER)) // only specified entities can be freezed
560                 return;
561
562         if(targ.frozen)
563                 return;
564
565         float targ_maxhealth = ((targ.flags & FL_MONSTER) ? targ.max_health : start_health);
566
567         targ.frozen = frozen_type;
568         targ.revive_progress = ((frozen_type == 3) ? 1 : 0);
569         targ.health = ((frozen_type == 3) ? targ_maxhealth : 1);
570         targ.revive_speed = freeze_time;
571
572         entity ice, head;
573         ice = spawn();
574         ice.owner = targ;
575         ice.classname = "ice";
576         ice.scale = targ.scale;
577         ice.think = Ice_Think;
578         ice.nextthink = time;
579         ice.frame = floor(random() * 21); // ice model has 20 different looking frames
580         setmodel(ice, "models/ice/ice.md3");
581         ice.alpha = 1;
582         ice.colormod = Team_ColorRGB(targ.team);
583         ice.glowmod = ice.colormod;
584         targ.iceblock = ice;
585         targ.revival_time = 0;
586
587         entity oldself;
588         oldself = self;
589         self = ice;
590         Ice_Think();
591         self = oldself;
592
593         RemoveGrapplingHook(targ);
594
595         FOR_EACH_PLAYER(head)
596         if(head.hook.aiment == targ)
597                 RemoveGrapplingHook(head);
598
599         // add waypoint
600         if(show_waypoint)
601                 WaypointSprite_Spawn("frozen", 0, 0, targ, '0 0 64', world, targ.team, targ, waypointsprite_attached, true, RADARICON_WAYPOINT, '0.25 0.90 1');
602 }
603
604 void Unfreeze (entity targ)
605 {
606         if(targ.frozen && targ.frozen != 3) // only reset health if target was frozen
607                 targ.health = ((IS_PLAYER(targ)) ? start_health : targ.max_health);
608
609         entity head;
610         targ.frozen = 0;
611         targ.revive_progress = 0;
612         targ.revival_time = time;
613
614         WaypointSprite_Kill(targ.waypointsprite_attached);
615
616         FOR_EACH_PLAYER(head)
617         if(head.hook.aiment == targ)
618                 RemoveGrapplingHook(head);
619
620         // remove the ice block
621         if(targ.iceblock)
622                 remove(targ.iceblock);
623         targ.iceblock = world;
624 }
625
626 void Damage (entity targ, entity inflictor, entity attacker, float damage, float deathtype, vector hitloc, vector force)
627 {
628         float mirrordamage;
629         float mirrorforce;
630         float complainteamdamage = 0;
631         entity attacker_save;
632         mirrordamage = 0;
633         mirrorforce = 0;
634
635         if (gameover || targ.killcount == -666)
636                 return;
637
638         entity oldself;
639         oldself = self;
640         self = targ;
641         damage_targ = targ;
642         damage_inflictor = inflictor;
643         damage_attacker = attacker;
644                 attacker_save = attacker;
645
646         if(IS_PLAYER(targ))
647                 if(targ.hook)
648                         if(targ.hook.aiment)
649                                 if(targ.hook.aiment == attacker)
650                                         RemoveGrapplingHook(targ); // STOP THAT, you parasite!
651
652         // special rule: gravity bomb does not hit team mates (other than for disconnecting the hook)
653         if(DEATH_ISWEAPON(deathtype, WEP_HOOK) || DEATH_ISWEAPON(deathtype, WEP_TUBA))
654         {
655                 if(IS_PLAYER(targ))
656                         if(SAME_TEAM(targ, attacker))
657                         {
658                                 self = oldself;
659                                 return;
660                         }
661         }
662
663         if(deathtype == DEATH_KILL || deathtype == DEATH_TEAMCHANGE || deathtype == DEATH_AUTOTEAMCHANGE)
664         {
665                 // exit the vehicle before killing (fixes a crash)
666                 if(IS_PLAYER(targ) && targ.vehicle)
667                         vehicles_exit(VHEF_RELESE);
668
669                 // These are ALWAYS lethal
670                 // No damage modification here
671                 // Instead, prepare the victim for his death...
672                 targ.armorvalue = 0;
673                 targ.spawnshieldtime = 0;
674                 targ.health = 0.9; // this is < 1
675                 targ.flags -= targ.flags & FL_GODMODE;
676                 damage = 100000;
677         }
678         else if(deathtype == DEATH_MIRRORDAMAGE || deathtype == DEATH_NOAMMO)
679         {
680                 // no processing
681         }
682         else
683         {
684                 // nullify damage if teamplay is on
685                 if(deathtype != DEATH_TELEFRAG)
686                 if(IS_PLAYER(attacker))
687                 {
688                         if(IS_PLAYER(targ) && targ != attacker && (IS_INDEPENDENT_PLAYER(attacker) || IS_INDEPENDENT_PLAYER(targ)))
689                         {
690                                 damage = 0;
691                                 force = '0 0 0';
692                         }
693                         else if(SAME_TEAM(attacker, targ))
694                         {
695                                 if(autocvar_teamplay_mode == 1)
696                                         damage = 0;
697                                 else if(attacker != targ)
698                                 {
699                                         if(autocvar_teamplay_mode == 3)
700                                                 damage = 0;
701                                         else if(autocvar_teamplay_mode == 4)
702                                         {
703                                                 if(IS_PLAYER(targ) && targ.deadflag == DEAD_NO)
704                                                 {
705                                                         attacker.dmg_team = attacker.dmg_team + damage;
706                                                         complainteamdamage = attacker.dmg_team - autocvar_g_teamdamage_threshold;
707                                                         if(complainteamdamage > 0)
708                                                                 mirrordamage = autocvar_g_mirrordamage * complainteamdamage;
709                                                         mirrorforce = autocvar_g_mirrordamage * vlen(force);
710                                                         damage = autocvar_g_friendlyfire * damage;
711                                                         // mirrordamage will be used LATER
712
713                                                         if(autocvar_g_mirrordamage_virtual)
714                                                         {
715                                                                 vector v  = healtharmor_applydamage(attacker.armorvalue, autocvar_g_balance_armor_blockpercent, deathtype, mirrordamage);
716                                                                 attacker.dmg_take += v.x;
717                                                                 attacker.dmg_save += v.y;
718                                                                 attacker.dmg_inflictor = inflictor;
719                                                                 mirrordamage = v.z;
720                                                                 mirrorforce = 0;
721                                                         }
722
723                                                         if(autocvar_g_friendlyfire_virtual)
724                                                         {
725                                                                 vector v = healtharmor_applydamage(targ.armorvalue, autocvar_g_balance_armor_blockpercent, deathtype, damage);
726                                                                 targ.dmg_take += v.x;
727                                                                 targ.dmg_save += v.y;
728                                                                 targ.dmg_inflictor = inflictor;
729                                                                 damage = 0;
730                                                                 if(!autocvar_g_friendlyfire_virtual_force)
731                                                                         force = '0 0 0';
732                                                         }
733                                                 }
734                                                 else
735                                                         damage = 0;
736                                         }
737                                 }
738                         }
739                 }
740
741                 if (!DEATH_ISSPECIAL(deathtype))
742                 {
743                         damage *= g_weapondamagefactor;
744                         mirrordamage *= g_weapondamagefactor;
745                         complainteamdamage *= g_weapondamagefactor;
746                         force = force * g_weaponforcefactor;
747                         mirrorforce *= g_weaponforcefactor;
748                 }
749
750                 // should this be changed at all? If so, in what way?
751                 frag_attacker = attacker;
752                 frag_target = targ;
753                 frag_damage = damage;
754                 frag_force = force;
755                 frag_deathtype = deathtype;
756                 frag_mirrordamage = mirrordamage;
757                 MUTATOR_CALLHOOK(PlayerDamage_Calculate);
758                 damage = frag_damage;
759                 mirrordamage = frag_mirrordamage;
760                 force = frag_force;
761
762                 if(targ.frozen)
763                 if(deathtype != DEATH_HURTTRIGGER && deathtype != DEATH_TEAMCHANGE && deathtype != DEATH_AUTOTEAMCHANGE)
764                 {
765                         if(autocvar_g_freezetag_revive_falldamage > 0)
766                         if(deathtype == DEATH_FALL)
767                         if(damage >= autocvar_g_freezetag_revive_falldamage)
768                         {
769                                 Unfreeze(targ);
770                                 targ.health = autocvar_g_freezetag_revive_falldamage_health;
771                                 pointparticles(particleeffectnum("iceorglass"), targ.origin, '0 0 0', 3);
772                                 Send_Notification(NOTIF_ALL, world, MSG_INFO, INFO_FREEZETAG_REVIVED_FALL, targ.netname);
773                                 Send_Notification(NOTIF_ONE, targ, MSG_CENTER, CENTER_FREEZETAG_REVIVE_SELF);
774                         }
775
776                         damage = 0;
777                         force *= autocvar_g_freezetag_frozen_force;
778                 }
779
780                 if(targ.frozen && deathtype == DEATH_HURTTRIGGER && !autocvar_g_freezetag_frozen_damage_trigger)
781                 {
782                         pointparticles(particleeffectnum("teleport"), targ.origin, '0 0 0', 1);
783
784                         entity oldself = self;
785                         self = targ;
786                         entity spot = SelectSpawnPoint (false);
787
788                         if(spot)
789                         {
790                                 damage = 0;
791                                 self.deadflag = DEAD_NO;
792
793                                 self.angles = spot.angles;
794
795                                 self.effects = 0;
796                                 self.effects |= EF_TELEPORT_BIT;
797
798                                 self.angles_z = 0; // never spawn tilted even if the spot says to
799                                 self.fixangle = true; // turn this way immediately
800                                 self.velocity = '0 0 0';
801                                 self.avelocity = '0 0 0';
802                                 self.punchangle = '0 0 0';
803                                 self.punchvector = '0 0 0';
804                                 self.oldvelocity = self.velocity;
805
806                                 self.spawnorigin = spot.origin;
807                                 setorigin (self, spot.origin + '0 0 1' * (1 - self.mins.z - 24));
808                                 // don't reset back to last position, even if new position is stuck in solid
809                                 self.oldorigin = self.origin;
810                                 self.prevorigin = self.origin;
811
812                                 pointparticles(particleeffectnum("teleport"), self.origin, '0 0 0', 1);
813                         }
814
815                         self = oldself;
816                 }
817
818                 if(!g_instagib)
819                 {
820                         // apply strength multiplier
821                         if (attacker.items & IT_STRENGTH)
822                         {
823                                 if(targ == attacker)
824                                 {
825                                         damage = damage * autocvar_g_balance_powerup_strength_selfdamage;
826                                         force = force * autocvar_g_balance_powerup_strength_selfforce;
827                                 }
828                                 else
829                                 {
830                                         damage = damage * autocvar_g_balance_powerup_strength_damage;
831                                         force = force * autocvar_g_balance_powerup_strength_force;
832                                 }
833                         }
834
835                         // apply invincibility multiplier
836                         if (targ.items & IT_INVINCIBLE)
837                                 damage = damage * autocvar_g_balance_powerup_invincible_takedamage;
838                 }
839
840                 if (targ == attacker)
841                         damage = damage * autocvar_g_balance_selfdamagepercent; // Partial damage if the attacker hits himself
842
843                 // count the damage
844                 if(attacker)
845                 if(!targ.deadflag)
846                 if(deathtype != DEATH_BUFF_VENGEANCE)
847                 if(targ.takedamage == DAMAGE_AIM)
848                 if(targ != attacker)
849                 {
850                         entity victim;
851                         if((targ.vehicle_flags & VHF_ISVEHICLE) && targ.owner)
852                                 victim = targ.owner;
853                         else
854                                 victim = targ;
855
856                         if(IS_PLAYER(victim) || (victim.turrcaps_flags & TFL_TURRCAPS_ISTURRET) || (victim.flags & FL_MONSTER))
857                         {
858                                 if(DIFF_TEAM(victim, attacker) && !victim.frozen)
859                                 {
860                                         if(damage > 0)
861                                         {
862                                                 if(deathtype != DEATH_FIRE)
863                                                 {
864                                                         if(victim.BUTTON_CHAT)
865                                                                 attacker.typehitsound += 1;
866                                                         else
867                                                                 attacker.damage_dealt += damage;
868                                                 }
869
870                                                 damage_goodhits += 1;
871                                                 damage_gooddamage += damage;
872
873                                                 if (!DEATH_ISSPECIAL(deathtype))
874                                                 {
875                                                         if(IS_PLAYER(targ)) // don't do this for vehicles
876                                                         if(IsFlying(victim))
877                                                                 yoda = 1;
878                                                 }
879                                         }
880                                 }
881                                 else
882                                 {
883                                         if(deathtype != DEATH_FIRE)
884                                         {
885                                                 attacker.typehitsound += 1;
886                                         }
887                                         if(complainteamdamage > 0)
888                                                 if(time > attacker.teamkill_complain)
889                                                 {
890                                                         attacker.teamkill_complain = time + 5;
891                                                         attacker.teamkill_soundtime = time + 0.4;
892                                                         attacker.teamkill_soundsource = targ;
893                                                 }
894                                 }
895                         }
896                 }
897         }
898
899         // apply push
900         if (self.damageforcescale)
901         if (vlen(force))
902         if (!IS_PLAYER(self) || time >= self.spawnshieldtime || self == attacker)
903         {
904                 vector farce = damage_explosion_calcpush(self.damageforcescale * force, self.velocity, autocvar_g_balance_damagepush_speedfactor);
905                 if(self.movetype == MOVETYPE_PHYSICS)
906                 {
907                         entity farcent;
908                         farcent = spawn();
909                         farcent.classname = "farce";
910                         farcent.enemy = self;
911                         farcent.movedir = farce * 10;
912                         if(self.mass)
913                                 farcent.movedir = farcent.movedir * self.mass;
914                         farcent.origin = hitloc;
915                         farcent.forcetype = FORCETYPE_FORCEATPOS;
916                         farcent.nextthink = time + 0.1;
917                         farcent.think = SUB_Remove;
918                 }
919                 else
920                         self.velocity = self.velocity + farce;
921                 self.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, float 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, float 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 }