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