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