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