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