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