3 #include <common/effects/all.qh>
6 #include <server/mutators/_mod.qh>
9 #include "spawnpoints.qh"
10 #include "../common/state.qh"
11 #include "../common/physics/player.qh"
12 #include "../common/t_items.qh"
13 #include "resources.qh"
14 #include "../common/vehicles/all.qh"
15 #include "../common/items/_mod.qh"
16 #include "../common/mutators/mutator/waypoints/waypointsprites.qh"
17 #include "../common/mutators/mutator/instagib/sv_instagib.qh"
18 #include "../common/mutators/mutator/buffs/buffs.qh"
19 #include "weapons/accuracy.qh"
20 #include "weapons/csqcprojectile.qh"
21 #include "weapons/selection.qh"
22 #include "../common/constants.qh"
23 #include "../common/deathtypes/all.qh"
24 #include "../common/notifications/all.qh"
25 #include "../common/physics/movetypes/movetypes.qh"
26 #include "../common/playerstats.qh"
27 #include "../common/teams.qh"
28 #include "../common/util.qh"
29 #include <common/gamemodes/rules.qh>
30 #include <common/weapons/_all.qh>
31 #include "../lib/csqcmodel/sv_model.qh"
32 #include "../lib/warpzone/common.qh"
34 void UpdateFrags(entity player, int f)
36 GameRules_scoring_add_team(player, SCORE, f);
39 void GiveFrags(entity attacker, entity targ, float f, int deathtype, .entity weaponentity)
41 // TODO route through PlayerScores instead
42 if(game_stopped) return;
49 GameRules_scoring_add(attacker, SUICIDES, 1);
54 GameRules_scoring_add(attacker, TEAMKILLS, 1);
60 GameRules_scoring_add(attacker, KILLS, 1);
61 if(!warmup_stage && targ.playerid)
62 PlayerStats_GameReport_Event_Player(attacker, sprintf("kills-%d", targ.playerid), 1);
65 GameRules_scoring_add(targ, DEATHS, 1);
67 // FIXME fix the mess this is (we have REAL points now!)
68 if(MUTATOR_CALLHOOK(GiveFragsForKill, attacker, targ, f, deathtype, attacker.(weaponentity)))
71 attacker.totalfrags += f;
74 UpdateFrags(attacker, f);
77 string AppendItemcodes(string s, entity player)
79 for(int slot = 0; slot < MAX_WEAPONSLOTS; ++slot)
81 .entity weaponentity = weaponentities[slot];
82 int w = player.(weaponentity).m_weapon.m_id;
84 w = player.(weaponentity).cnt; // previous weapon
85 if(w != 0 || slot == 0)
86 s = strcat(s, ftos(w));
88 if(time < STAT(STRENGTH_FINISHED, player))
90 if(time < STAT(INVINCIBLE_FINISHED, player))
92 if(PHYS_INPUT_BUTTON_CHAT(player))
94 // TODO: include these codes as a flag on the item itself
95 MUTATOR_CALLHOOK(LogDeath_AppendItemCodes, player, s);
96 s = M_ARGV(1, string);
100 void LogDeath(string mode, int deathtype, entity killer, entity killed)
103 if(!autocvar_sv_eventlog)
105 s = strcat(":kill:", mode);
106 s = strcat(s, ":", ftos(killer.playerid));
107 s = strcat(s, ":", ftos(killed.playerid));
108 s = strcat(s, ":type=", Deathtype_Name(deathtype));
109 s = strcat(s, ":items=");
110 s = AppendItemcodes(s, killer);
113 s = strcat(s, ":victimitems=");
114 s = AppendItemcodes(s, killed);
119 void Obituary_SpecialDeath(
123 string s1, string s2, string s3,
124 float f1, float f2, float f3)
126 if(!DEATH_ISSPECIAL(deathtype))
128 backtrace("Obituary_SpecialDeath called without a special deathtype?\n");
132 entity deathent = REGISTRY_GET(Deathtypes, deathtype - DT_FIRST);
135 backtrace("Obituary_SpecialDeath: Could not find deathtype entity!\n");
139 if(g_cts && deathtype == DEATH_KILL.m_id)
140 return; // TODO: somehow put this in CTS gamemode file!
142 Notification death_message = (murder) ? deathent.death_msgmurder : deathent.death_msgself;
145 Send_Notification_WOCOVA(
153 Send_Notification_WOCOVA(
157 death_message.nent_msginfo,
164 float Obituary_WeaponDeath(
168 string s1, string s2, string s3,
171 Weapon death_weapon = DEATH_WEAPONOF(deathtype);
172 if (death_weapon == WEP_Null)
175 w_deathtype = deathtype;
176 Notification death_message = ((murder) ? death_weapon.wr_killmessage(death_weapon) : death_weapon.wr_suicidemessage(death_weapon));
181 Send_Notification_WOCOVA(
189 // send the info part to everyone
190 Send_Notification_WOCOVA(
194 death_message.nent_msginfo,
202 "Obituary_WeaponDeath(): ^1Deathtype ^7(%d)^1 has no notification for weapon %s!\n",
211 bool frag_centermessage_override(entity attacker, entity targ, int deathtype, int kill_count_to_attacker, int kill_count_to_target)
213 if(deathtype == DEATH_FIRE.m_id)
215 Send_Notification(NOTIF_ONE, attacker, MSG_CHOICE, CHOICE_FRAG_FIRE, targ.netname, kill_count_to_attacker, (IS_BOT_CLIENT(targ) ? -1 : CS(targ).ping));
216 Send_Notification(NOTIF_ONE, targ, MSG_CHOICE, CHOICE_FRAGGED_FIRE, attacker.netname, kill_count_to_target, GetResource(attacker, RES_HEALTH), GetResource(attacker, RES_ARMOR), (IS_BOT_CLIENT(attacker) ? -1 : CS(attacker).ping));
220 return MUTATOR_CALLHOOK(FragCenterMessage, attacker, targ, deathtype, kill_count_to_attacker, kill_count_to_target);
223 void Obituary(entity attacker, entity inflictor, entity targ, int deathtype, .entity weaponentity)
226 if (!IS_PLAYER(targ)) { backtrace("Obituary called on non-player?!\n"); return; }
229 float notif_firstblood = false;
230 float kill_count_to_attacker, kill_count_to_target;
232 // Set final information for the death
233 targ.death_origin = targ.origin;
234 string deathlocation = (autocvar_notification_server_allows_location ? NearestLocation(targ.death_origin) : "");
236 #ifdef NOTIFICATIONS_DEBUG
239 "Obituary(%s, %s, %s, %s = %d);\n",
243 Deathtype_Name(deathtype),
254 if(DEATH_ISSPECIAL(deathtype))
256 if(deathtype == DEATH_TEAMCHANGE.m_id || deathtype == DEATH_AUTOTEAMCHANGE.m_id)
258 Obituary_SpecialDeath(targ, false, deathtype, targ.netname, deathlocation, "", targ.team, 0, 0);
262 switch(DEATH_ENT(deathtype))
264 case DEATH_MIRRORDAMAGE:
266 Obituary_SpecialDeath(targ, false, deathtype, targ.netname, deathlocation, "", CS(targ).killcount, 0, 0);
272 Obituary_SpecialDeath(targ, false, deathtype, targ.netname, deathlocation, "", CS(targ).killcount, 0, 0);
278 else if (!Obituary_WeaponDeath(targ, false, deathtype, targ.netname, deathlocation, "", CS(targ).killcount, 0))
280 backtrace("SUICIDE: what the hell happened here?\n");
283 LogDeath("suicide", deathtype, targ, targ);
284 if(deathtype != DEATH_AUTOTEAMCHANGE.m_id) // special case: don't negate frags if auto switched
285 GiveFrags(attacker, targ, -1, deathtype, weaponentity);
291 else if(IS_PLAYER(attacker))
293 if(SAME_TEAM(attacker, targ))
295 LogDeath("tk", deathtype, attacker, targ);
296 GiveFrags(attacker, targ, -1, deathtype, weaponentity);
298 CS(attacker).killcount = 0;
300 Send_Notification(NOTIF_ONE, attacker, MSG_CENTER, CENTER_DEATH_TEAMKILL_FRAG, targ.netname);
301 Send_Notification(NOTIF_ONE, targ, MSG_CENTER, CENTER_DEATH_TEAMKILL_FRAGGED, attacker.netname);
302 Send_Notification(NOTIF_ALL, NULL, MSG_INFO, APP_TEAM_NUM(targ.team, INFO_DEATH_TEAMKILL), targ.netname, attacker.netname, deathlocation, CS(targ).killcount);
304 // In this case, the death message will ALWAYS be "foo was betrayed by bar"
305 // No need for specific death/weapon messages...
309 LogDeath("frag", deathtype, attacker, targ);
310 GiveFrags(attacker, targ, 1, deathtype, weaponentity);
312 CS(attacker).taunt_soundtime = time + 1;
313 CS(attacker).killcount = CS(attacker).killcount + 1;
315 attacker.killsound += 1;
317 // TODO: improve SPREE_ITEM and KILL_SPREE_LIST
318 // these 2 macros are spread over multiple files
319 #define SPREE_ITEM(counta,countb,center,normal,gentle) \
321 Send_Notification(NOTIF_ONE, attacker, MSG_ANNCE, ANNCE_KILLSTREAK_##countb); \
323 PlayerStats_GameReport_Event_Player(attacker, PLAYERSTATS_ACHIEVEMENT_KILL_SPREE_##counta, 1); \
326 switch(CS(attacker).killcount)
333 if(!warmup_stage && !checkrules_firstblood)
335 checkrules_firstblood = true;
336 notif_firstblood = true; // modify the current messages so that they too show firstblood information
337 PlayerStats_GameReport_Event_Player(attacker, PLAYERSTATS_ACHIEVEMENT_FIRSTBLOOD, 1);
338 PlayerStats_GameReport_Event_Player(targ, PLAYERSTATS_ACHIEVEMENT_FIRSTVICTIM, 1);
340 // tell spree_inf and spree_cen that this is a first-blood and first-victim event
341 kill_count_to_attacker = -1;
342 kill_count_to_target = -2;
346 kill_count_to_attacker = CS(attacker).killcount;
347 kill_count_to_target = 0;
358 kill_count_to_attacker,
359 (IS_BOT_CLIENT(targ) ? -1 : CS(targ).ping)
367 kill_count_to_target,
368 GetResource(attacker, RES_HEALTH),
369 GetResource(attacker, RES_ARMOR),
370 (IS_BOT_CLIENT(attacker) ? -1 : CS(attacker).ping)
373 else if(!frag_centermessage_override(attacker, targ, deathtype, kill_count_to_attacker, kill_count_to_target))
381 kill_count_to_attacker,
382 (IS_BOT_CLIENT(targ) ? -1 : CS(targ).ping)
390 kill_count_to_target,
391 GetResource(attacker, RES_HEALTH),
392 GetResource(attacker, RES_ARMOR),
393 (IS_BOT_CLIENT(attacker) ? -1 : CS(attacker).ping)
398 if(deathtype == DEATH_BUFF.m_id)
399 f3 = buff_FirstFromFlags(STAT(BUFFS, attacker)).m_id;
401 if (!Obituary_WeaponDeath(targ, true, deathtype, targ.netname, attacker.netname, deathlocation, CS(targ).killcount, kill_count_to_attacker))
402 Obituary_SpecialDeath(targ, true, deathtype, targ.netname, attacker.netname, deathlocation, CS(targ).killcount, kill_count_to_attacker, f3);
411 switch(DEATH_ENT(deathtype))
413 // For now, we're just forcing HURTTRIGGER to behave as "DEATH_VOID" and giving it no special options...
414 // Later on you will only be able to make custom messages using DEATH_CUSTOM,
415 // and there will be a REAL DEATH_VOID implementation which mappers will use.
416 case DEATH_HURTTRIGGER:
418 Obituary_SpecialDeath(targ, false, deathtype,
430 Obituary_SpecialDeath(targ, false, deathtype,
432 ((strstrofs(deathmessage, "%", 0) < 0) ? strcat("%s ", deathmessage) : deathmessage),
442 Obituary_SpecialDeath(targ, false, deathtype, targ.netname, deathlocation, "", CS(targ).killcount, 0, 0);
447 LogDeath("accident", deathtype, targ, targ);
448 GiveFrags(targ, targ, -1, deathtype, weaponentity);
450 if(GameRules_scoring_add(targ, SCORE, 0) == -5)
452 Send_Notification(NOTIF_ONE, targ, MSG_ANNCE, ANNCE_ACHIEVEMENT_BOTLIKE);
455 PlayerStats_GameReport_Event_Player(attacker, PLAYERSTATS_ACHIEVEMENT_BOTLIKE, 1);
460 // reset target kill count
461 CS(targ).killcount = 0;
464 void Ice_Think(entity this)
466 if(!STAT(FROZEN, this.owner) || this.owner.iceblock != this)
471 vector ice_org = this.owner.origin - '0 0 16';
472 if (this.origin != ice_org)
473 setorigin(this, ice_org);
474 this.nextthink = time;
477 void Freeze(entity targ, float revivespeed, int frozen_type, bool show_waypoint)
479 if(!IS_PLAYER(targ) && !IS_MONSTER(targ)) // TODO: only specified entities can be freezed
482 if(STAT(FROZEN, targ))
485 float targ_maxhealth = ((IS_MONSTER(targ)) ? targ.max_health : start_health);
487 STAT(FROZEN, targ) = frozen_type;
488 STAT(REVIVE_PROGRESS, targ) = ((frozen_type == FROZEN_TEMP_DYING) ? 1 : 0);
489 SetResource(targ, RES_HEALTH, ((frozen_type == FROZEN_TEMP_DYING) ? targ_maxhealth : 1));
490 targ.revive_speed = revivespeed;
492 IL_REMOVE(g_bot_targets, targ);
493 targ.bot_attack = false;
494 targ.freeze_time = time;
496 entity ice = new(ice);
498 ice.scale = targ.scale;
499 // set_movetype(ice, MOVETYPE_FOLLOW) would rotate the ice model with the player
500 setthink(ice, Ice_Think);
501 ice.nextthink = time;
502 ice.frame = floor(random() * 21); // ice model has 20 different looking frames
503 setmodel(ice, MDL_ICE);
505 ice.colormod = Team_ColorRGB(targ.team);
506 ice.glowmod = ice.colormod;
508 targ.revival_time = 0;
512 RemoveGrapplingHooks(targ);
514 FOREACH_CLIENT(IS_PLAYER(it),
516 for(int slot = 0; slot < MAX_WEAPONSLOTS; ++slot)
518 .entity weaponentity = weaponentities[slot];
519 if(it.(weaponentity).hook.aiment == targ)
520 RemoveHook(it.(weaponentity).hook);
525 if(MUTATOR_CALLHOOK(Freeze, targ, revivespeed, frozen_type) || show_waypoint)
526 WaypointSprite_Spawn(WP_Frozen, 0, 0, targ, '0 0 64', NULL, targ.team, targ, waypointsprite_attached, true, RADARICON_WAYPOINT);
529 void Unfreeze(entity targ, bool reset_health)
531 if(!STAT(FROZEN, targ))
534 if (reset_health && STAT(FROZEN, targ) != FROZEN_TEMP_DYING)
535 SetResource(targ, RES_HEALTH, ((IS_PLAYER(targ)) ? start_health : targ.max_health));
537 targ.pauseregen_finished = time + autocvar_g_balance_pause_health_regen;
539 STAT(FROZEN, targ) = 0;
540 STAT(REVIVE_PROGRESS, targ) = 0;
541 targ.revival_time = time;
543 IL_PUSH(g_bot_targets, targ);
544 targ.bot_attack = true;
546 WaypointSprite_Kill(targ.waypointsprite_attached);
548 FOREACH_CLIENT(IS_PLAYER(it),
550 for(int slot = 0; slot < MAX_WEAPONSLOTS; ++slot)
552 .entity weaponentity = weaponentities[slot];
553 if(it.(weaponentity).hook.aiment == targ)
554 RemoveHook(it.(weaponentity).hook);
558 // remove the ice block
560 delete(targ.iceblock);
561 targ.iceblock = NULL;
563 MUTATOR_CALLHOOK(Unfreeze, targ);
566 void Damage(entity targ, entity inflictor, entity attacker, float damage, int deathtype, .entity weaponentity, vector hitloc, vector force)
568 float complainteamdamage = 0;
569 float mirrordamage = 0;
570 float mirrorforce = 0;
572 if (game_stopped || (IS_CLIENT(targ) && CS(targ).killcount == FRAGS_SPECTATOR))
575 entity attacker_save = attacker;
577 // special rule: gravity bomb does not hit team mates (other than for disconnecting the hook)
578 if(DEATH_ISWEAPON(deathtype, WEP_HOOK) || DEATH_ISWEAPON(deathtype, WEP_TUBA))
580 if(IS_PLAYER(targ) && SAME_TEAM(targ, attacker))
586 if(deathtype == DEATH_KILL.m_id || deathtype == DEATH_TEAMCHANGE.m_id || deathtype == DEATH_AUTOTEAMCHANGE.m_id)
588 // exit the vehicle before killing (fixes a crash)
589 if(IS_PLAYER(targ) && targ.vehicle)
590 vehicles_exit(targ.vehicle, VHEF_RELEASE);
592 // These are ALWAYS lethal
593 // No damage modification here
594 // Instead, prepare the victim for his death...
595 SetResourceExplicit(targ, RES_ARMOR, 0);
596 targ.spawnshieldtime = 0;
597 SetResourceExplicit(targ, RES_HEALTH, 0.9); // this is < 1
598 targ.flags -= targ.flags & FL_GODMODE;
601 else if(deathtype == DEATH_MIRRORDAMAGE.m_id || deathtype == DEATH_NOAMMO.m_id)
607 // nullify damage if teamplay is on
608 if(deathtype != DEATH_TELEFRAG.m_id)
609 if(IS_PLAYER(attacker))
611 if(IS_PLAYER(targ) && targ != attacker && (IS_INDEPENDENT_PLAYER(attacker) || IS_INDEPENDENT_PLAYER(targ)))
616 else if(SAME_TEAM(attacker, targ))
618 if(autocvar_teamplay_mode == 1)
620 else if(attacker != targ)
622 if(autocvar_teamplay_mode == 3)
624 else if(autocvar_teamplay_mode == 4)
626 if(IS_PLAYER(targ) && !IS_DEAD(targ))
628 attacker.dmg_team = attacker.dmg_team + damage;
629 complainteamdamage = attacker.dmg_team - autocvar_g_teamdamage_threshold;
630 if(complainteamdamage > 0)
631 mirrordamage = autocvar_g_mirrordamage * complainteamdamage;
632 mirrorforce = autocvar_g_mirrordamage * vlen(force);
633 damage = autocvar_g_friendlyfire * damage;
634 // mirrordamage will be used LATER
636 if(autocvar_g_mirrordamage_virtual)
638 vector v = healtharmor_applydamage(GetResource(attacker, RES_ARMOR), autocvar_g_balance_armor_blockpercent, deathtype, mirrordamage);
639 attacker.dmg_take += v.x;
640 attacker.dmg_save += v.y;
641 attacker.dmg_inflictor = inflictor;
646 if(autocvar_g_friendlyfire_virtual)
648 vector v = healtharmor_applydamage(GetResource(targ, RES_ARMOR), autocvar_g_balance_armor_blockpercent, deathtype, damage);
649 targ.dmg_take += v.x;
650 targ.dmg_save += v.y;
651 targ.dmg_inflictor = inflictor;
653 if(!autocvar_g_friendlyfire_virtual_force)
657 else if(!targ.canteamdamage)
664 if (!DEATH_ISSPECIAL(deathtype))
666 damage *= g_weapondamagefactor;
667 mirrordamage *= g_weapondamagefactor;
668 complainteamdamage *= g_weapondamagefactor;
669 force = force * g_weaponforcefactor;
670 mirrorforce *= g_weaponforcefactor;
673 // should this be changed at all? If so, in what way?
674 MUTATOR_CALLHOOK(Damage_Calculate, inflictor, attacker, targ, deathtype, damage, mirrordamage, force, attacker.(weaponentity));
675 damage = M_ARGV(4, float);
676 mirrordamage = M_ARGV(5, float);
677 force = M_ARGV(6, vector);
679 if(IS_PLAYER(targ) && damage > 0 && attacker)
681 for(int slot = 0; slot < MAX_WEAPONSLOTS; ++slot)
683 .entity went = weaponentities[slot];
684 if(targ.(went).hook && targ.(went).hook.aiment == attacker)
685 RemoveHook(targ.(went).hook);
689 if(STAT(FROZEN, targ) && !ITEM_DAMAGE_NEEDKILL(deathtype)
690 && deathtype != DEATH_TEAMCHANGE.m_id && deathtype != DEATH_AUTOTEAMCHANGE.m_id)
692 if(autocvar_g_frozen_revive_falldamage > 0 && deathtype == DEATH_FALL.m_id && damage >= autocvar_g_frozen_revive_falldamage)
694 Unfreeze(targ, false);
695 SetResource(targ, RES_HEALTH, autocvar_g_frozen_revive_falldamage_health);
696 Send_Effect(EFFECT_ICEORGLASS, targ.origin, '0 0 0', 3);
697 Send_Notification(NOTIF_ALL, NULL, MSG_INFO, INFO_FREEZETAG_REVIVED_FALL, targ.netname);
698 Send_Notification(NOTIF_ONE, targ, MSG_CENTER, CENTER_FREEZETAG_REVIVE_SELF);
702 force *= autocvar_g_frozen_force;
705 if(IS_PLAYER(targ) && STAT(FROZEN, targ)
706 && ITEM_DAMAGE_NEEDKILL(deathtype) && !autocvar_g_frozen_damage_trigger)
708 Send_Effect(EFFECT_TELEPORT, targ.origin, '0 0 0', 1);
710 entity spot = SelectSpawnPoint(targ, false);
714 targ.deadflag = DEAD_NO;
716 targ.angles = spot.angles;
719 targ.effects |= EF_TELEPORT_BIT;
721 targ.angles_z = 0; // never spawn tilted even if the spot says to
722 targ.fixangle = true; // turn this way immediately
723 targ.velocity = '0 0 0';
724 targ.avelocity = '0 0 0';
725 targ.punchangle = '0 0 0';
726 targ.punchvector = '0 0 0';
727 targ.oldvelocity = targ.velocity;
729 targ.spawnorigin = spot.origin;
730 setorigin(targ, spot.origin + '0 0 1' * (1 - targ.mins.z - 24));
731 // don't reset back to last position, even if new position is stuck in solid
732 targ.oldorigin = targ.origin;
734 Send_Effect(EFFECT_TELEPORT, targ.origin, '0 0 0', 1);
738 if(!MUTATOR_IS_ENABLED(mutator_instagib))
740 // apply strength multiplier
741 if (attacker.items & ITEM_Strength.m_itemid)
745 damage = damage * autocvar_g_balance_powerup_strength_selfdamage;
746 force = force * autocvar_g_balance_powerup_strength_selfforce;
750 damage = damage * autocvar_g_balance_powerup_strength_damage;
751 force = force * autocvar_g_balance_powerup_strength_force;
755 // apply invincibility multiplier
756 if (targ.items & ITEM_Shield.m_itemid)
758 damage = damage * autocvar_g_balance_powerup_invincible_takedamage;
759 if (targ != attacker)
761 force = force * autocvar_g_balance_powerup_invincible_takeforce;
766 if (targ == attacker)
767 damage = damage * autocvar_g_balance_selfdamagepercent; // Partial damage if the attacker hits himself
772 if(deathtype != DEATH_BUFF.m_id)
773 if(targ.takedamage == DAMAGE_AIM)
777 if(IS_VEHICLE(targ) && targ.owner)
782 if(IS_PLAYER(victim) || (IS_TURRET(victim) && victim.active == ACTIVE_ACTIVE) || IS_MONSTER(victim) || MUTATOR_CALLHOOK(PlayHitsound, victim, attacker))
784 if(DIFF_TEAM(victim, attacker) && !STAT(FROZEN, victim))
788 if(deathtype != DEATH_FIRE.m_id)
790 if(PHYS_INPUT_BUTTON_CHAT(victim))
791 attacker.typehitsound += 1;
793 attacker.damage_dealt += damage;
796 damage_goodhits += 1;
797 damage_gooddamage += damage;
799 if (!DEATH_ISSPECIAL(deathtype))
801 if(IS_PLAYER(targ)) // don't do this for vehicles
807 else if(IS_PLAYER(attacker))
809 // if enemy gets frozen in this frame and receives other damage don't
810 // play the typehitsound e.g. when hit by multiple bullets of the shotgun
811 if (deathtype != DEATH_FIRE.m_id && (!STAT(FROZEN, victim) || time > victim.freeze_time))
813 attacker.typehitsound += 1;
815 if(complainteamdamage > 0)
816 if(time > CS(attacker).teamkill_complain)
818 CS(attacker).teamkill_complain = time + 5;
819 CS(attacker).teamkill_soundtime = time + 0.4;
820 CS(attacker).teamkill_soundsource = targ;
828 if (targ.damageforcescale)
830 if (!IS_PLAYER(targ) || time >= targ.spawnshieldtime || targ == attacker)
832 vector farce = damage_explosion_calcpush(targ.damageforcescale * force, targ.velocity, autocvar_g_balance_damagepush_speedfactor);
833 if(targ.move_movetype == MOVETYPE_PHYSICS)
835 entity farcent = new(farce);
836 farcent.enemy = targ;
837 farcent.movedir = farce * 10;
839 farcent.movedir = farcent.movedir * targ.mass;
840 farcent.origin = hitloc;
841 farcent.forcetype = FORCETYPE_FORCEATPOS;
842 farcent.nextthink = time + 0.1;
843 setthink(farcent, SUB_Remove);
847 targ.velocity = targ.velocity + farce;
849 UNSET_ONGROUND(targ);
850 UpdateCSQCProjectile(targ);
853 if (damage != 0 || (targ.damageforcescale && force))
854 if (targ.event_damage)
855 targ.event_damage (targ, inflictor, attacker, damage, deathtype, weaponentity, hitloc, force);
857 // apply mirror damage if any
858 if(!autocvar_g_mirrordamage_onlyweapons || DEATH_WEAPONOF(deathtype) != WEP_Null)
859 if(mirrordamage > 0 || mirrorforce > 0)
861 attacker = attacker_save;
863 force = normalize(attacker.origin + attacker.view_ofs - hitloc) * mirrorforce;
864 Damage(attacker, inflictor, attacker, mirrordamage, DEATH_MIRRORDAMAGE.m_id, weaponentity, attacker.origin, force);
868 float RadiusDamageForSource (entity inflictor, vector inflictororigin, vector inflictorvelocity, entity attacker, float coredamage, float edgedamage, float rad, entity cantbe, entity mustbe,
869 float inflictorselfdamage, float forceintensity, float forcezscale, int deathtype, .entity weaponentity, entity directhitentity)
870 // Returns total damage applies to creatures
874 float total_damage_to_creatures;
879 float stat_damagedone;
881 if(RadiusDamage_running)
883 backtrace("RadiusDamage called recursively! Expect stuff to go HORRIBLY wrong.");
887 RadiusDamage_running = 1;
889 tfloordmg = autocvar_g_throughfloor_damage;
890 tfloorforce = autocvar_g_throughfloor_force;
892 total_damage_to_creatures = 0;
894 if(deathtype != (WEP_HOOK.m_id | HITTYPE_SECONDARY | HITTYPE_BOUNCE)) // only send gravity bomb damage once
895 if(DEATH_WEAPONOF(deathtype) != WEP_TUBA) // do not send tuba damage (bandwidth hog)
897 force = inflictorvelocity;
901 force = normalize(force);
902 if(forceintensity >= 0)
903 Damage_DamageInfo(inflictororigin, coredamage, edgedamage, rad, forceintensity * force, deathtype, 0, attacker);
905 Damage_DamageInfo(inflictororigin, coredamage, edgedamage, -rad, (-forceintensity) * force, deathtype, 0, attacker);
910 targ = WarpZone_FindRadius (inflictororigin, rad + MAX_DAMAGEEXTRARADIUS, false);
914 if ((targ != inflictor) || inflictorselfdamage)
915 if (((cantbe != targ) && !mustbe) || (mustbe == targ))
922 // LordHavoc: measure distance to nearest point on target (not origin)
923 // (this guarentees 100% damage on a touch impact)
924 nearest = targ.WarpZone_findradius_nearest;
925 diff = targ.WarpZone_findradius_dist;
926 // round up a little on the damage to ensure full damage on impacts
927 // and turn the distance into a fraction of the radius
928 power = 1 - ((vlen (diff) - bound(MIN_DAMAGEEXTRARADIUS, targ.damageextraradius, MAX_DAMAGEEXTRARADIUS)) / rad);
930 //bprint(ftos(power));
931 //if (targ == attacker)
932 // print(ftos(power), "\n");
938 finaldmg = coredamage * power + edgedamage * (1 - power);
944 vector myblastorigin;
947 myblastorigin = WarpZone_TransformOrigin(targ, inflictororigin);
949 // if it's a player, use the view origin as reference
950 center = CENTER_OR_VIEWOFS(targ);
952 force = normalize(center - myblastorigin);
953 force = force * (finaldmg / coredamage) * forceintensity;
956 // apply special scaling along the z axis if set
957 // NOTE: 0 value is not allowed for compatibility, in the case of weapon cvars not being set
959 force.z *= forcezscale;
961 if(targ != directhitentity)
966 float mininv_f, mininv_d;
968 // test line of sight to multiple positions on box,
969 // and do damage if any of them hit
972 // we know: max stddev of hitratio = 1 / (2 * sqrt(n))
973 // so for a given max stddev:
974 // n = (1 / (2 * max stddev of hitratio))^2
976 mininv_d = (finaldmg * (1-tfloordmg)) / autocvar_g_throughfloor_damage_max_stddev;
977 mininv_f = (vlen(force) * (1-tfloorforce)) / autocvar_g_throughfloor_force_max_stddev;
979 if(autocvar_g_throughfloor_debug)
980 LOG_INFOF("THROUGHFLOOR: D=%f F=%f max(dD)=1/%f max(dF)=1/%f", finaldmg, vlen(force), mininv_d, mininv_f);
983 total = 0.25 * (max(mininv_f, mininv_d) ** 2);
985 if(autocvar_g_throughfloor_debug)
986 LOG_INFOF(" steps=%f", total);
990 total = ceil(bound(autocvar_g_throughfloor_min_steps_player, total, autocvar_g_throughfloor_max_steps_player));
992 total = ceil(bound(autocvar_g_throughfloor_min_steps_other, total, autocvar_g_throughfloor_max_steps_other));
994 if(autocvar_g_throughfloor_debug)
995 LOG_INFOF(" steps=%f dD=%f dF=%f", total, finaldmg * (1-tfloordmg) / (2 * sqrt(total)), vlen(force) * (1-tfloorforce) / (2 * sqrt(total)));
997 for(c = 0; c < total; ++c)
999 //traceline(targ.WarpZone_findradius_findorigin, nearest, MOVE_NOMONSTERS, inflictor);
1000 WarpZone_TraceLine(inflictororigin, WarpZone_UnTransformOrigin(targ, nearest), MOVE_NOMONSTERS, inflictor);
1001 if (trace_fraction == 1 || trace_ent == targ)
1005 hitloc = hitloc + nearest;
1009 nearest.x = targ.origin.x + targ.mins.x + random() * targ.size.x;
1010 nearest.y = targ.origin.y + targ.mins.y + random() * targ.size.y;
1011 nearest.z = targ.origin.z + targ.mins.z + random() * targ.size.z;
1014 nearest = hitloc * (1 / max(1, hits));
1015 hitratio = (hits / total);
1016 a = bound(0, tfloordmg + (1-tfloordmg) * hitratio, 1);
1017 finaldmg = finaldmg * a;
1018 a = bound(0, tfloorforce + (1-tfloorforce) * hitratio, 1);
1021 if(autocvar_g_throughfloor_debug)
1022 LOG_INFOF(" D=%f F=%f", finaldmg, vlen(force));
1025 //if (targ == attacker)
1027 // print("hits ", ftos(hits), " / ", ftos(total));
1028 // print(" finaldmg ", ftos(finaldmg), " force ", vtos(force));
1029 // print(" (", ftos(a), ")\n");
1031 if(finaldmg || force)
1035 total_damage_to_creatures += finaldmg;
1037 if(accuracy_isgooddamage(attacker, targ))
1038 stat_damagedone += finaldmg;
1041 if(targ == directhitentity || DEATH_ISSPECIAL(deathtype))
1042 Damage(targ, inflictor, attacker, finaldmg, deathtype, weaponentity, nearest, force);
1044 Damage(targ, inflictor, attacker, finaldmg, deathtype | HITTYPE_SPLASH, weaponentity, nearest, force);
1052 RadiusDamage_running = 0;
1054 if(!DEATH_ISSPECIAL(deathtype))
1055 accuracy_add(attacker, DEATH_WEAPONOF(deathtype), 0, min(coredamage, stat_damagedone));
1057 return total_damage_to_creatures;
1060 float RadiusDamage(entity inflictor, entity attacker, float coredamage, float edgedamage, float rad, entity cantbe, entity mustbe, float forceintensity, int deathtype, .entity weaponentity, entity directhitentity)
1062 return RadiusDamageForSource(inflictor, (inflictor.origin + (inflictor.mins + inflictor.maxs) * 0.5), inflictor.velocity, attacker, coredamage, edgedamage, rad,
1063 cantbe, mustbe, false, forceintensity, 1, deathtype, weaponentity, directhitentity);
1066 bool Heal(entity targ, entity inflictor, float amount, float limit)
1068 if(game_stopped || (IS_CLIENT(targ) && CS(targ).killcount == FRAGS_SPECTATOR) || STAT(FROZEN, targ) || IS_DEAD(targ))
1071 bool healed = false;
1073 healed = targ.event_heal(targ, inflictor, amount, limit);
1074 // TODO: additional handling? what if the healing kills them? should this abort if healing would do so etc
1075 // TODO: healing fx!
1076 // TODO: armor healing?
1080 float Fire_IsBurning(entity e)
1082 return (time < e.fire_endtime);
1085 float Fire_AddDamage(entity e, entity o, float d, float t, float dt)
1088 float maxtime, mintime, maxdamage, mindamage, maxdps, mindps, totaldamage, totaltime;
1099 // print("adding a fire burner to ", e.classname, "\n");
1100 e.fire_burner = new(fireburner);
1101 setthink(e.fire_burner, fireburner_think);
1102 e.fire_burner.nextthink = time;
1103 e.fire_burner.owner = e;
1109 if(Fire_IsBurning(e))
1111 mintime = e.fire_endtime - time;
1112 maxtime = max(mintime, t);
1114 mindps = e.fire_damagepersec;
1115 maxdps = max(mindps, dps);
1117 if(maxtime > mintime || maxdps > mindps)
1121 // damage we have right now
1122 mindamage = mindps * mintime;
1124 // damage we want to get
1125 maxdamage = mindamage + d;
1127 // but we can't exceed maxtime * maxdps!
1128 totaldamage = min(maxdamage, maxtime * maxdps);
1132 // totaldamage = min(mindamage + d, maxtime * maxdps)
1134 // totaldamage <= maxtime * maxdps
1135 // ==> totaldamage / maxdps <= maxtime.
1137 // totaldamage / mindps = min(mindamage / mindps + d, maxtime * maxdps / mindps)
1138 // >= min(mintime, maxtime)
1139 // ==> totaldamage / maxdps >= mintime.
1142 // how long do we damage then?
1143 // at least as long as before
1144 // but, never exceed maxdps
1145 totaltime = max(mintime, totaldamage / maxdps); // always <= maxtime due to lemma
1149 // at most as long as maximum allowed
1150 // but, never below mindps
1151 totaltime = min(maxtime, totaldamage / mindps); // always >= mintime due to lemma
1153 // assuming t > mintime, dps > mindps:
1154 // we get d = t * dps = maxtime * maxdps
1155 // totaldamage = min(maxdamage, maxtime * maxdps) = min(... + d, maxtime * maxdps) = maxtime * maxdps
1156 // totaldamage / maxdps = maxtime
1157 // totaldamage / mindps > totaldamage / maxdps = maxtime
1159 // a) totaltime = max(mintime, maxtime) = maxtime
1160 // b) totaltime = min(maxtime, totaldamage / maxdps) = maxtime
1162 // assuming t <= mintime:
1163 // we get maxtime = mintime
1164 // a) totaltime = max(mintime, ...) >= mintime, also totaltime <= maxtime by the lemma, therefore totaltime = mintime = maxtime
1165 // b) totaltime = min(maxtime, ...) <= maxtime, also totaltime >= mintime by the lemma, therefore totaltime = mintime = maxtime
1167 // assuming dps <= mindps:
1168 // we get mindps = maxdps.
1169 // With this, the lemma says that mintime <= totaldamage / mindps = totaldamage / maxdps <= maxtime.
1170 // a) totaltime = max(mintime, totaldamage / maxdps) = totaldamage / maxdps
1171 // b) totaltime = min(maxtime, totaldamage / mindps) = totaldamage / maxdps
1173 e.fire_damagepersec = totaldamage / totaltime;
1174 e.fire_endtime = time + totaltime;
1175 if(totaldamage > 1.2 * mindamage)
1177 e.fire_deathtype = dt;
1178 if(e.fire_owner != o)
1181 e.fire_hitsound = false;
1184 if(accuracy_isgooddamage(o, e))
1185 accuracy_add(o, DEATH_WEAPONOF(dt), 0, max(0, totaldamage - mindamage));
1186 return max(0, totaldamage - mindamage); // can never be negative, but to make sure
1193 e.fire_damagepersec = dps;
1194 e.fire_endtime = time + t;
1195 e.fire_deathtype = dt;
1197 e.fire_hitsound = false;
1198 if(accuracy_isgooddamage(o, e))
1199 accuracy_add(o, DEATH_WEAPONOF(dt), 0, d);
1204 void Fire_ApplyDamage(entity e)
1209 if (!Fire_IsBurning(e))
1212 for(t = 0, o = e.owner; o.owner && t < 16; o = o.owner, ++t);
1213 if(IS_NOT_A_CLIENT(o))
1216 // water and slime stop fire
1218 if(e.watertype != CONTENT_LAVA)
1225 t = min(frametime, e.fire_endtime - time);
1226 d = e.fire_damagepersec * t;
1228 hi = e.fire_owner.damage_dealt;
1229 ty = e.fire_owner.typehitsound;
1230 Damage(e, e, e.fire_owner, d, e.fire_deathtype, DMG_NOWEP, e.origin, '0 0 0');
1231 if(e.fire_hitsound && e.fire_owner)
1233 e.fire_owner.damage_dealt = hi;
1234 e.fire_owner.typehitsound = ty;
1236 e.fire_hitsound = true;
1238 if(!IS_INDEPENDENT_PLAYER(e) && !STAT(FROZEN, e))
1240 IL_EACH(g_damagedbycontents, it.damagedbycontents && it != e,
1242 if(!IS_DEAD(it) && it.takedamage && !IS_INDEPENDENT_PLAYER(it))
1243 if(boxesoverlap(e.absmin, e.absmax, it.absmin, it.absmax))
1245 t = autocvar_g_balance_firetransfer_time * (e.fire_endtime - time);
1246 d = autocvar_g_balance_firetransfer_damage * e.fire_damagepersec * t;
1247 Fire_AddDamage(it, o, d, t, DEATH_FIRE.m_id);
1253 void Fire_ApplyEffect(entity e)
1255 if(Fire_IsBurning(e))
1256 e.effects |= EF_FLAME;
1258 e.effects &= ~EF_FLAME;
1261 void fireburner_think(entity this)
1263 // for players, this is done in the regular loop
1264 if(wasfreed(this.owner))
1269 Fire_ApplyEffect(this.owner);
1270 if(!Fire_IsBurning(this.owner))
1272 this.owner.fire_burner = NULL;
1276 Fire_ApplyDamage(this.owner);
1277 this.nextthink = time;