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