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