3 #include <common/effects/all.qh>
6 #include <server/gamelog.qh>
7 #include <server/items/items.qh>
8 #include <server/mutators/_mod.qh>
11 #include "spawnpoints.qh"
12 #include "../common/state.qh"
13 #include "../common/physics/player.qh"
14 #include "resources.qh"
15 #include "../common/vehicles/all.qh"
16 #include "../common/items/_mod.qh"
17 #include "../common/mutators/mutator/waypoints/waypointsprites.qh"
18 #include "../common/mutators/mutator/instagib/sv_instagib.qh"
19 #include "../common/mutators/mutator/buffs/buffs.qh"
20 #include "weapons/accuracy.qh"
21 #include "weapons/csqcprojectile.qh"
22 #include "weapons/selection.qh"
23 #include "../common/constants.qh"
24 #include "../common/deathtypes/all.qh"
25 #include <common/mapobjects/defs.qh>
26 #include "../common/notifications/all.qh"
27 #include "../common/physics/movetypes/movetypes.qh"
28 #include "../common/playerstats.qh"
29 #include "../common/teams.qh"
30 #include "../common/util.qh"
31 #include <common/gamemodes/_mod.qh>
32 #include <common/gamemodes/rules.qh>
33 #include <common/weapons/_all.qh>
34 #include "../lib/csqcmodel/sv_model.qh"
35 #include "../lib/warpzone/common.qh"
37 void UpdateFrags(entity player, int f)
39 GameRules_scoring_add_team(player, SCORE, f);
42 void GiveFrags(entity attacker, entity targ, float f, int deathtype, .entity weaponentity)
44 // TODO route through PlayerScores instead
45 if(game_stopped) return;
52 GameRules_scoring_add(attacker, SUICIDES, 1);
57 GameRules_scoring_add(attacker, TEAMKILLS, 1);
63 GameRules_scoring_add(attacker, KILLS, 1);
64 if(!warmup_stage && targ.playerid)
65 PlayerStats_GameReport_Event_Player(attacker, sprintf("kills-%d", targ.playerid), 1);
68 GameRules_scoring_add(targ, DEATHS, 1);
70 // FIXME fix the mess this is (we have REAL points now!)
71 if(MUTATOR_CALLHOOK(GiveFragsForKill, attacker, targ, f, deathtype, attacker.(weaponentity)))
74 attacker.totalfrags += f;
77 UpdateFrags(attacker, f);
80 string AppendItemcodes(string s, entity player)
82 for(int slot = 0; slot < MAX_WEAPONSLOTS; ++slot)
84 .entity weaponentity = weaponentities[slot];
85 int w = player.(weaponentity).m_weapon.m_id;
87 w = player.(weaponentity).cnt; // previous weapon
88 if(w != 0 || slot == 0)
89 s = strcat(s, ftos(w));
91 if(time < STAT(STRENGTH_FINISHED, player))
93 if(time < STAT(INVINCIBLE_FINISHED, player))
95 if(PHYS_INPUT_BUTTON_CHAT(player))
97 // TODO: include these codes as a flag on the item itself
98 MUTATOR_CALLHOOK(LogDeath_AppendItemCodes, player, s);
99 s = M_ARGV(1, string);
103 void LogDeath(string mode, int deathtype, entity killer, entity killed)
106 if(!autocvar_sv_eventlog)
108 s = strcat(":kill:", mode);
109 s = strcat(s, ":", ftos(killer.playerid));
110 s = strcat(s, ":", ftos(killed.playerid));
111 s = strcat(s, ":type=", Deathtype_Name(deathtype));
112 s = strcat(s, ":items=");
113 s = AppendItemcodes(s, killer);
116 s = strcat(s, ":victimitems=");
117 s = AppendItemcodes(s, killed);
122 void Obituary_SpecialDeath(
126 string s1, string s2, string s3,
127 float f1, float f2, float f3)
129 if(!DEATH_ISSPECIAL(deathtype))
131 backtrace("Obituary_SpecialDeath called without a special deathtype?\n");
135 entity deathent = REGISTRY_GET(Deathtypes, deathtype - DT_FIRST);
138 backtrace("Obituary_SpecialDeath: Could not find deathtype entity!\n");
142 if(g_cts && deathtype == DEATH_KILL.m_id)
143 return; // TODO: somehow put this in CTS gamemode file!
145 Notification death_message = (murder) ? deathent.death_msgmurder : deathent.death_msgself;
148 Send_Notification_WOCOVA(
156 Send_Notification_WOCOVA(
160 death_message.nent_msginfo,
167 float Obituary_WeaponDeath(
171 string s1, string s2, string s3,
174 Weapon death_weapon = DEATH_WEAPONOF(deathtype);
175 if (death_weapon == WEP_Null)
178 w_deathtype = deathtype;
179 Notification death_message = ((murder) ? death_weapon.wr_killmessage(death_weapon) : death_weapon.wr_suicidemessage(death_weapon));
184 Send_Notification_WOCOVA(
192 // send the info part to everyone
193 Send_Notification_WOCOVA(
197 death_message.nent_msginfo,
205 "Obituary_WeaponDeath(): ^1Deathtype ^7(%d)^1 has no notification for weapon %s!\n",
214 bool frag_centermessage_override(entity attacker, entity targ, int deathtype, int kill_count_to_attacker, int kill_count_to_target, string attacker_name)
216 if(deathtype == DEATH_FIRE.m_id)
218 Send_Notification(NOTIF_ONE, attacker, MSG_CHOICE, CHOICE_FRAG_FIRE, targ.netname, kill_count_to_attacker, (IS_BOT_CLIENT(targ) ? -1 : CS(targ).ping));
219 Send_Notification(NOTIF_ONE, targ, MSG_CHOICE, CHOICE_FRAGGED_FIRE, attacker_name, kill_count_to_target, GetResource(attacker, RES_HEALTH), GetResource(attacker, RES_ARMOR), (IS_BOT_CLIENT(attacker) ? -1 : CS(attacker).ping));
223 return MUTATOR_CALLHOOK(FragCenterMessage, attacker, targ, deathtype, kill_count_to_attacker, kill_count_to_target);
226 void Obituary(entity attacker, entity inflictor, entity targ, int deathtype, .entity weaponentity)
229 if (!IS_PLAYER(targ)) { backtrace("Obituary called on non-player?!\n"); return; }
232 float notif_firstblood = false;
233 float kill_count_to_attacker, kill_count_to_target;
234 bool notif_anonymous = false;
235 string attacker_name = attacker.netname;
237 // Set final information for the death
238 targ.death_origin = targ.origin;
239 string deathlocation = (autocvar_notification_server_allows_location ? NearestLocation(targ.death_origin) : "");
241 // Abort now if a mutator requests it
242 if (MUTATOR_CALLHOOK(ClientObituary, inflictor, attacker, targ, deathtype, attacker.(weaponentity))) { CS(targ).killcount = 0; return; }
243 notif_anonymous = M_ARGV(5, bool);
246 attacker_name = "Anonymous player";
248 #ifdef NOTIFICATIONS_DEBUG
251 "Obituary(%s, %s, %s, %s = %d);\n",
255 Deathtype_Name(deathtype),
266 if(DEATH_ISSPECIAL(deathtype))
268 if(deathtype == DEATH_TEAMCHANGE.m_id || deathtype == DEATH_AUTOTEAMCHANGE.m_id)
270 Obituary_SpecialDeath(targ, false, deathtype, targ.netname, deathlocation, "", targ.team, 0, 0);
274 switch(DEATH_ENT(deathtype))
276 case DEATH_MIRRORDAMAGE:
278 Obituary_SpecialDeath(targ, false, deathtype, targ.netname, deathlocation, "", CS(targ).killcount, 0, 0);
284 Obituary_SpecialDeath(targ, false, deathtype, targ.netname, deathlocation, "", CS(targ).killcount, 0, 0);
290 else if (!Obituary_WeaponDeath(targ, false, deathtype, targ.netname, deathlocation, "", CS(targ).killcount, 0))
292 backtrace("SUICIDE: what the hell happened here?\n");
295 LogDeath("suicide", deathtype, targ, targ);
296 if(deathtype != DEATH_AUTOTEAMCHANGE.m_id) // special case: don't negate frags if auto switched
297 GiveFrags(attacker, targ, -1, deathtype, weaponentity);
303 else if(IS_PLAYER(attacker))
305 if(SAME_TEAM(attacker, targ))
307 LogDeath("tk", deathtype, attacker, targ);
308 GiveFrags(attacker, targ, -1, deathtype, weaponentity);
310 CS(attacker).killcount = 0;
312 Send_Notification(NOTIF_ONE, attacker, MSG_CENTER, CENTER_DEATH_TEAMKILL_FRAG, targ.netname);
313 Send_Notification(NOTIF_ONE, targ, MSG_CENTER, CENTER_DEATH_TEAMKILL_FRAGGED, attacker_name);
314 Send_Notification(NOTIF_ALL, NULL, MSG_INFO, APP_TEAM_NUM(targ.team, INFO_DEATH_TEAMKILL), targ.netname, attacker_name, deathlocation, CS(targ).killcount);
316 // In this case, the death message will ALWAYS be "foo was betrayed by bar"
317 // No need for specific death/weapon messages...
321 LogDeath("frag", deathtype, attacker, targ);
322 GiveFrags(attacker, targ, 1, deathtype, weaponentity);
324 CS(attacker).taunt_soundtime = time + 1;
325 CS(attacker).killcount = CS(attacker).killcount + 1;
327 attacker.killsound += 1;
329 // TODO: improve SPREE_ITEM and KILL_SPREE_LIST
330 // these 2 macros are spread over multiple files
331 #define SPREE_ITEM(counta,countb,center,normal,gentle) \
333 Send_Notification(NOTIF_ONE, attacker, MSG_ANNCE, ANNCE_KILLSTREAK_##countb); \
335 PlayerStats_GameReport_Event_Player(attacker, PLAYERSTATS_ACHIEVEMENT_KILL_SPREE_##counta, 1); \
338 switch(CS(attacker).killcount)
345 if(!warmup_stage && !checkrules_firstblood)
347 checkrules_firstblood = true;
348 notif_firstblood = true; // modify the current messages so that they too show firstblood information
349 PlayerStats_GameReport_Event_Player(attacker, PLAYERSTATS_ACHIEVEMENT_FIRSTBLOOD, 1);
350 PlayerStats_GameReport_Event_Player(targ, PLAYERSTATS_ACHIEVEMENT_FIRSTVICTIM, 1);
352 // tell spree_inf and spree_cen that this is a first-blood and first-victim event
353 kill_count_to_attacker = -1;
354 kill_count_to_target = -2;
358 kill_count_to_attacker = CS(attacker).killcount;
359 kill_count_to_target = 0;
370 kill_count_to_attacker,
371 (IS_BOT_CLIENT(targ) ? -1 : CS(targ).ping)
379 kill_count_to_target,
380 GetResource(attacker, RES_HEALTH),
381 GetResource(attacker, RES_ARMOR),
382 (IS_BOT_CLIENT(attacker) ? -1 : CS(attacker).ping)
385 else if(!frag_centermessage_override(attacker, targ, deathtype, kill_count_to_attacker, kill_count_to_target, attacker_name))
393 kill_count_to_attacker,
394 (IS_BOT_CLIENT(targ) ? -1 : CS(targ).ping)
402 kill_count_to_target,
403 GetResource(attacker, RES_HEALTH),
404 GetResource(attacker, RES_ARMOR),
405 (IS_BOT_CLIENT(attacker) ? -1 : CS(attacker).ping)
410 if(deathtype == DEATH_BUFF.m_id)
411 f3 = buff_FirstFromFlags(STAT(BUFFS, attacker)).m_id;
413 if (!Obituary_WeaponDeath(targ, true, deathtype, targ.netname, attacker_name, deathlocation, CS(targ).killcount, kill_count_to_attacker))
414 Obituary_SpecialDeath(targ, true, deathtype, targ.netname, attacker_name, deathlocation, CS(targ).killcount, kill_count_to_attacker, f3);
423 switch(DEATH_ENT(deathtype))
425 // For now, we're just forcing HURTTRIGGER to behave as "DEATH_VOID" and giving it no special options...
426 // Later on you will only be able to make custom messages using DEATH_CUSTOM,
427 // and there will be a REAL DEATH_VOID implementation which mappers will use.
428 case DEATH_HURTTRIGGER:
430 Obituary_SpecialDeath(targ, false, deathtype,
442 Obituary_SpecialDeath(targ, false, deathtype,
444 ((strstrofs(deathmessage, "%", 0) < 0) ? strcat("%s ", deathmessage) : deathmessage),
454 Obituary_SpecialDeath(targ, false, deathtype, targ.netname, deathlocation, "", CS(targ).killcount, 0, 0);
459 LogDeath("accident", deathtype, targ, targ);
460 GiveFrags(targ, targ, -1, deathtype, weaponentity);
462 if(GameRules_scoring_add(targ, SCORE, 0) == -5)
464 Send_Notification(NOTIF_ONE, targ, MSG_ANNCE, ANNCE_ACHIEVEMENT_BOTLIKE);
467 PlayerStats_GameReport_Event_Player(attacker, PLAYERSTATS_ACHIEVEMENT_BOTLIKE, 1);
472 // reset target kill count
473 CS(targ).killcount = 0;
476 void Ice_Think(entity this)
478 if(!STAT(FROZEN, this.owner) || this.owner.iceblock != this)
483 vector ice_org = this.owner.origin - '0 0 16';
484 if (this.origin != ice_org)
485 setorigin(this, ice_org);
486 this.nextthink = time;
489 void Freeze(entity targ, float revivespeed, int frozen_type, bool show_waypoint)
491 if(!IS_PLAYER(targ) && !IS_MONSTER(targ)) // TODO: only specified entities can be freezed
494 if(STAT(FROZEN, targ))
497 float targ_maxhealth = ((IS_MONSTER(targ)) ? targ.max_health : start_health);
499 STAT(FROZEN, targ) = frozen_type;
500 STAT(REVIVE_PROGRESS, targ) = ((frozen_type == FROZEN_TEMP_DYING) ? 1 : 0);
501 SetResource(targ, RES_HEALTH, ((frozen_type == FROZEN_TEMP_DYING) ? targ_maxhealth : 1));
502 targ.revive_speed = revivespeed;
504 IL_REMOVE(g_bot_targets, targ);
505 targ.bot_attack = false;
506 targ.freeze_time = time;
508 entity ice = new(ice);
510 ice.scale = targ.scale;
511 // set_movetype(ice, MOVETYPE_FOLLOW) would rotate the ice model with the player
512 setthink(ice, Ice_Think);
513 ice.nextthink = time;
514 ice.frame = floor(random() * 21); // ice model has 20 different looking frames
515 setmodel(ice, MDL_ICE);
517 ice.colormod = Team_ColorRGB(targ.team);
518 ice.glowmod = ice.colormod;
520 targ.revival_time = 0;
524 RemoveGrapplingHooks(targ);
526 FOREACH_CLIENT(IS_PLAYER(it),
528 for(int slot = 0; slot < MAX_WEAPONSLOTS; ++slot)
530 .entity weaponentity = weaponentities[slot];
531 if(it.(weaponentity).hook.aiment == targ)
532 RemoveHook(it.(weaponentity).hook);
537 if(MUTATOR_CALLHOOK(Freeze, targ, revivespeed, frozen_type) || show_waypoint)
538 WaypointSprite_Spawn(WP_Frozen, 0, 0, targ, '0 0 64', NULL, targ.team, targ, waypointsprite_attached, true, RADARICON_WAYPOINT);
541 void Unfreeze(entity targ, bool reset_health)
543 if(!STAT(FROZEN, targ))
546 if (reset_health && STAT(FROZEN, targ) != FROZEN_TEMP_DYING)
547 SetResource(targ, RES_HEALTH, ((IS_PLAYER(targ)) ? start_health : targ.max_health));
549 targ.pauseregen_finished = time + autocvar_g_balance_pause_health_regen;
551 STAT(FROZEN, targ) = 0;
552 STAT(REVIVE_PROGRESS, targ) = 0;
553 targ.revival_time = time;
555 IL_PUSH(g_bot_targets, targ);
556 targ.bot_attack = true;
558 WaypointSprite_Kill(targ.waypointsprite_attached);
560 FOREACH_CLIENT(IS_PLAYER(it),
562 for(int slot = 0; slot < MAX_WEAPONSLOTS; ++slot)
564 .entity weaponentity = weaponentities[slot];
565 if(it.(weaponentity).hook.aiment == targ)
566 RemoveHook(it.(weaponentity).hook);
570 // remove the ice block
572 delete(targ.iceblock);
573 targ.iceblock = NULL;
575 MUTATOR_CALLHOOK(Unfreeze, targ);
578 void Damage(entity targ, entity inflictor, entity attacker, float damage, int deathtype, .entity weaponentity, vector hitloc, vector force)
580 float complainteamdamage = 0;
581 float mirrordamage = 0;
582 float mirrorforce = 0;
584 if (game_stopped || (IS_CLIENT(targ) && CS(targ).killcount == FRAGS_SPECTATOR))
587 entity attacker_save = attacker;
589 // special rule: gravity bombs and sound-based attacks do not affect team mates (other than for disconnecting the hook)
590 if(DEATH_ISWEAPON(deathtype, WEP_HOOK) || (deathtype & HITTYPE_SOUND))
592 if(IS_PLAYER(targ) && SAME_TEAM(targ, attacker))
598 if(deathtype == DEATH_KILL.m_id || deathtype == DEATH_TEAMCHANGE.m_id || deathtype == DEATH_AUTOTEAMCHANGE.m_id)
600 // exit the vehicle before killing (fixes a crash)
601 if(IS_PLAYER(targ) && targ.vehicle)
602 vehicles_exit(targ.vehicle, VHEF_RELEASE);
604 // These are ALWAYS lethal
605 // No damage modification here
606 // Instead, prepare the victim for his death...
607 SetResourceExplicit(targ, RES_ARMOR, 0);
608 targ.spawnshieldtime = 0;
609 SetResourceExplicit(targ, RES_HEALTH, 0.9); // this is < 1
610 targ.flags -= targ.flags & FL_GODMODE;
613 else if(deathtype == DEATH_MIRRORDAMAGE.m_id || deathtype == DEATH_NOAMMO.m_id)
619 // nullify damage if teamplay is on
620 if(deathtype != DEATH_TELEFRAG.m_id)
621 if(IS_PLAYER(attacker))
623 if(IS_PLAYER(targ) && targ != attacker && (IS_INDEPENDENT_PLAYER(attacker) || IS_INDEPENDENT_PLAYER(targ)))
628 else if(SAME_TEAM(attacker, targ))
630 if(autocvar_teamplay_mode == 1)
632 else if(attacker != targ)
634 if(autocvar_teamplay_mode == 2)
636 if(IS_PLAYER(targ) && !IS_DEAD(targ))
638 attacker.dmg_team = attacker.dmg_team + damage;
639 complainteamdamage = attacker.dmg_team - autocvar_g_teamdamage_threshold;
642 else if(autocvar_teamplay_mode == 3)
644 else if(autocvar_teamplay_mode == 4)
646 if(IS_PLAYER(targ) && !IS_DEAD(targ))
648 attacker.dmg_team = attacker.dmg_team + damage;
649 complainteamdamage = attacker.dmg_team - autocvar_g_teamdamage_threshold;
650 if(complainteamdamage > 0)
651 mirrordamage = autocvar_g_mirrordamage * complainteamdamage;
652 mirrorforce = autocvar_g_mirrordamage * vlen(force);
653 damage = autocvar_g_friendlyfire * damage;
654 // mirrordamage will be used LATER
656 if(autocvar_g_mirrordamage_virtual)
658 vector v = healtharmor_applydamage(GetResource(attacker, RES_ARMOR), autocvar_g_balance_armor_blockpercent, deathtype, mirrordamage);
659 attacker.dmg_take += v.x;
660 attacker.dmg_save += v.y;
661 attacker.dmg_inflictor = inflictor;
666 if(autocvar_g_friendlyfire_virtual)
668 vector v = healtharmor_applydamage(GetResource(targ, RES_ARMOR), autocvar_g_balance_armor_blockpercent, deathtype, damage);
669 targ.dmg_take += v.x;
670 targ.dmg_save += v.y;
671 targ.dmg_inflictor = inflictor;
673 if(!autocvar_g_friendlyfire_virtual_force)
677 else if(!targ.canteamdamage)
684 if (!DEATH_ISSPECIAL(deathtype))
686 damage *= g_weapondamagefactor;
687 mirrordamage *= g_weapondamagefactor;
688 complainteamdamage *= g_weapondamagefactor;
689 force = force * g_weaponforcefactor;
690 mirrorforce *= g_weaponforcefactor;
693 // should this be changed at all? If so, in what way?
694 MUTATOR_CALLHOOK(Damage_Calculate, inflictor, attacker, targ, deathtype, damage, mirrordamage, force, attacker.(weaponentity));
695 damage = M_ARGV(4, float);
696 mirrordamage = M_ARGV(5, float);
697 force = M_ARGV(6, vector);
699 if(IS_PLAYER(targ) && damage > 0 && attacker)
701 for(int slot = 0; slot < MAX_WEAPONSLOTS; ++slot)
703 .entity went = weaponentities[slot];
704 if(targ.(went).hook && targ.(went).hook.aiment == attacker)
705 RemoveHook(targ.(went).hook);
709 if(STAT(FROZEN, targ) && !ITEM_DAMAGE_NEEDKILL(deathtype)
710 && deathtype != DEATH_TEAMCHANGE.m_id && deathtype != DEATH_AUTOTEAMCHANGE.m_id)
712 if(autocvar_g_frozen_revive_falldamage > 0 && deathtype == DEATH_FALL.m_id && damage >= autocvar_g_frozen_revive_falldamage)
714 Unfreeze(targ, false);
715 SetResource(targ, RES_HEALTH, autocvar_g_frozen_revive_falldamage_health);
716 Send_Effect(EFFECT_ICEORGLASS, targ.origin, '0 0 0', 3);
717 Send_Notification(NOTIF_ALL, NULL, MSG_INFO, INFO_FREEZETAG_REVIVED_FALL, targ.netname);
718 Send_Notification(NOTIF_ONE, targ, MSG_CENTER, CENTER_FREEZETAG_REVIVE_SELF);
722 force *= autocvar_g_frozen_force;
725 if(IS_PLAYER(targ) && STAT(FROZEN, targ)
726 && ITEM_DAMAGE_NEEDKILL(deathtype) && !autocvar_g_frozen_damage_trigger)
728 Send_Effect(EFFECT_TELEPORT, targ.origin, '0 0 0', 1);
730 entity spot = SelectSpawnPoint(targ, false);
734 targ.deadflag = DEAD_NO;
736 targ.angles = spot.angles;
739 targ.effects |= EF_TELEPORT_BIT;
741 targ.angles_z = 0; // never spawn tilted even if the spot says to
742 targ.fixangle = true; // turn this way immediately
743 targ.velocity = '0 0 0';
744 targ.avelocity = '0 0 0';
745 targ.punchangle = '0 0 0';
746 targ.punchvector = '0 0 0';
747 targ.oldvelocity = targ.velocity;
749 targ.spawnorigin = spot.origin;
750 setorigin(targ, spot.origin + '0 0 1' * (1 - targ.mins.z - 24));
751 // don't reset back to last position, even if new position is stuck in solid
752 targ.oldorigin = targ.origin;
754 Send_Effect(EFFECT_TELEPORT, targ.origin, '0 0 0', 1);
758 if(!MUTATOR_IS_ENABLED(mutator_instagib))
760 // apply strength multiplier
761 if (attacker.items & ITEM_Strength.m_itemid)
765 damage = damage * autocvar_g_balance_powerup_strength_selfdamage;
766 force = force * autocvar_g_balance_powerup_strength_selfforce;
770 damage = damage * autocvar_g_balance_powerup_strength_damage;
771 force = force * autocvar_g_balance_powerup_strength_force;
775 // apply invincibility multiplier
776 if (targ.items & ITEM_Shield.m_itemid)
778 damage = damage * autocvar_g_balance_powerup_invincible_takedamage;
779 if (targ != attacker)
781 force = force * autocvar_g_balance_powerup_invincible_takeforce;
786 if (targ == attacker)
787 damage = damage * autocvar_g_balance_selfdamagepercent; // Partial damage if the attacker hits himself
792 if(deathtype != DEATH_BUFF.m_id)
793 if(targ.takedamage == DAMAGE_AIM)
797 if(IS_VEHICLE(targ) && targ.owner)
802 if(IS_PLAYER(victim) || (IS_TURRET(victim) && victim.active == ACTIVE_ACTIVE) || IS_MONSTER(victim) || MUTATOR_CALLHOOK(PlayHitsound, victim, attacker))
804 if(DIFF_TEAM(victim, attacker) && !STAT(FROZEN, victim))
808 if(deathtype != DEATH_FIRE.m_id)
810 if(PHYS_INPUT_BUTTON_CHAT(victim))
811 attacker.typehitsound += 1;
813 attacker.damage_dealt += damage;
816 damage_goodhits += 1;
817 damage_gooddamage += damage;
819 if (!DEATH_ISSPECIAL(deathtype))
821 if(IS_PLAYER(targ)) // don't do this for vehicles
827 else if(IS_PLAYER(attacker))
829 // if enemy gets frozen in this frame and receives other damage don't
830 // play the typehitsound e.g. when hit by multiple bullets of the shotgun
831 if (deathtype != DEATH_FIRE.m_id && (!STAT(FROZEN, victim) || time > victim.freeze_time))
833 attacker.typehitsound += 1;
835 if(complainteamdamage > 0)
836 if(time > CS(attacker).teamkill_complain)
838 CS(attacker).teamkill_complain = time + 5;
839 CS(attacker).teamkill_soundtime = time + 0.4;
840 CS(attacker).teamkill_soundsource = targ;
848 if (targ.damageforcescale)
850 if (!IS_PLAYER(targ) || time >= targ.spawnshieldtime || targ == attacker)
852 vector farce = damage_explosion_calcpush(targ.damageforcescale * force, targ.velocity, autocvar_g_balance_damagepush_speedfactor);
853 if(targ.move_movetype == MOVETYPE_PHYSICS)
855 entity farcent = new(farce);
856 farcent.enemy = targ;
857 farcent.movedir = farce * 10;
859 farcent.movedir = farcent.movedir * targ.mass;
860 farcent.origin = hitloc;
861 farcent.forcetype = FORCETYPE_FORCEATPOS;
862 farcent.nextthink = time + 0.1;
863 setthink(farcent, SUB_Remove);
865 else if(targ.move_movetype != MOVETYPE_NOCLIP)
867 targ.velocity = targ.velocity + farce;
869 UNSET_ONGROUND(targ);
870 UpdateCSQCProjectile(targ);
873 if (damage != 0 || (targ.damageforcescale && force))
874 if (targ.event_damage)
875 targ.event_damage (targ, inflictor, attacker, damage, deathtype, weaponentity, hitloc, force);
877 // apply mirror damage if any
878 if(!autocvar_g_mirrordamage_onlyweapons || DEATH_WEAPONOF(deathtype) != WEP_Null)
879 if(mirrordamage > 0 || mirrorforce > 0)
881 attacker = attacker_save;
883 force = normalize(attacker.origin + attacker.view_ofs - hitloc) * mirrorforce;
884 Damage(attacker, inflictor, attacker, mirrordamage, DEATH_MIRRORDAMAGE.m_id, weaponentity, attacker.origin, force);
888 float RadiusDamageForSource (entity inflictor, vector inflictororigin, vector inflictorvelocity, entity attacker, float coredamage, float edgedamage, float rad, entity cantbe, entity mustbe,
889 float inflictorselfdamage, float forceintensity, float forcezscale, int deathtype, .entity weaponentity, entity directhitentity)
890 // Returns total damage applies to creatures
894 float total_damage_to_creatures;
899 float stat_damagedone;
901 if(RadiusDamage_running)
903 backtrace("RadiusDamage called recursively! Expect stuff to go HORRIBLY wrong.");
907 RadiusDamage_running = 1;
909 tfloordmg = autocvar_g_throughfloor_damage;
910 tfloorforce = autocvar_g_throughfloor_force;
912 total_damage_to_creatures = 0;
914 if(deathtype != (WEP_HOOK.m_id | HITTYPE_SECONDARY | HITTYPE_BOUNCE)) // only send gravity bomb damage once
915 if(!(deathtype & HITTYPE_SOUND)) // do not send radial sound damage (bandwidth hog)
917 force = inflictorvelocity;
921 force = normalize(force);
922 if(forceintensity >= 0)
923 Damage_DamageInfo(inflictororigin, coredamage, edgedamage, rad, forceintensity * force, deathtype, 0, attacker);
925 Damage_DamageInfo(inflictororigin, coredamage, edgedamage, -rad, (-forceintensity) * force, deathtype, 0, attacker);
930 targ = WarpZone_FindRadius (inflictororigin, rad + MAX_DAMAGEEXTRARADIUS, false);
934 if ((targ != inflictor) || inflictorselfdamage)
935 if (((cantbe != targ) && !mustbe) || (mustbe == targ))
942 // LordHavoc: measure distance to nearest point on target (not origin)
943 // (this guarentees 100% damage on a touch impact)
944 nearest = targ.WarpZone_findradius_nearest;
945 diff = targ.WarpZone_findradius_dist;
946 // round up a little on the damage to ensure full damage on impacts
947 // and turn the distance into a fraction of the radius
948 power = 1 - ((vlen (diff) - bound(MIN_DAMAGEEXTRARADIUS, targ.damageextraradius, MAX_DAMAGEEXTRARADIUS)) / rad);
950 //bprint(ftos(power));
951 //if (targ == attacker)
952 // print(ftos(power), "\n");
958 finaldmg = coredamage * power + edgedamage * (1 - power);
964 vector myblastorigin;
967 myblastorigin = WarpZone_TransformOrigin(targ, inflictororigin);
969 // if it's a player, use the view origin as reference
970 center = CENTER_OR_VIEWOFS(targ);
972 force = normalize(center - myblastorigin);
973 force = force * (finaldmg / coredamage) * forceintensity;
976 // apply special scaling along the z axis if set
977 // NOTE: 0 value is not allowed for compatibility, in the case of weapon cvars not being set
979 force.z *= forcezscale;
981 if(targ != directhitentity)
986 float mininv_f, mininv_d;
988 // test line of sight to multiple positions on box,
989 // and do damage if any of them hit
992 // we know: max stddev of hitratio = 1 / (2 * sqrt(n))
993 // so for a given max stddev:
994 // n = (1 / (2 * max stddev of hitratio))^2
996 mininv_d = (finaldmg * (1-tfloordmg)) / autocvar_g_throughfloor_damage_max_stddev;
997 mininv_f = (vlen(force) * (1-tfloorforce)) / autocvar_g_throughfloor_force_max_stddev;
999 if(autocvar_g_throughfloor_debug)
1000 LOG_INFOF("THROUGHFLOOR: D=%f F=%f max(dD)=1/%f max(dF)=1/%f", finaldmg, vlen(force), mininv_d, mininv_f);
1003 total = 0.25 * (max(mininv_f, mininv_d) ** 2);
1005 if(autocvar_g_throughfloor_debug)
1006 LOG_INFOF(" steps=%f", total);
1009 if (IS_PLAYER(targ))
1010 total = ceil(bound(autocvar_g_throughfloor_min_steps_player, total, autocvar_g_throughfloor_max_steps_player));
1012 total = ceil(bound(autocvar_g_throughfloor_min_steps_other, total, autocvar_g_throughfloor_max_steps_other));
1014 if(autocvar_g_throughfloor_debug)
1015 LOG_INFOF(" steps=%f dD=%f dF=%f", total, finaldmg * (1-tfloordmg) / (2 * sqrt(total)), vlen(force) * (1-tfloorforce) / (2 * sqrt(total)));
1017 for(c = 0; c < total; ++c)
1019 //traceline(targ.WarpZone_findradius_findorigin, nearest, MOVE_NOMONSTERS, inflictor);
1020 WarpZone_TraceLine(inflictororigin, WarpZone_UnTransformOrigin(targ, nearest), MOVE_NOMONSTERS, inflictor);
1021 if (trace_fraction == 1 || trace_ent == targ)
1025 hitloc = hitloc + nearest;
1029 nearest.x = targ.origin.x + targ.mins.x + random() * targ.size.x;
1030 nearest.y = targ.origin.y + targ.mins.y + random() * targ.size.y;
1031 nearest.z = targ.origin.z + targ.mins.z + random() * targ.size.z;
1034 nearest = hitloc * (1 / max(1, hits));
1035 hitratio = (hits / total);
1036 a = bound(0, tfloordmg + (1-tfloordmg) * hitratio, 1);
1037 finaldmg = finaldmg * a;
1038 a = bound(0, tfloorforce + (1-tfloorforce) * hitratio, 1);
1041 if(autocvar_g_throughfloor_debug)
1042 LOG_INFOF(" D=%f F=%f", finaldmg, vlen(force));
1045 //if (targ == attacker)
1047 // print("hits ", ftos(hits), " / ", ftos(total));
1048 // print(" finaldmg ", ftos(finaldmg), " force ", vtos(force));
1049 // print(" (", ftos(a), ")\n");
1051 if(finaldmg || force)
1055 total_damage_to_creatures += finaldmg;
1057 if(accuracy_isgooddamage(attacker, targ))
1058 stat_damagedone += finaldmg;
1061 if(targ == directhitentity || DEATH_ISSPECIAL(deathtype))
1062 Damage(targ, inflictor, attacker, finaldmg, deathtype, weaponentity, nearest, force);
1064 Damage(targ, inflictor, attacker, finaldmg, deathtype | HITTYPE_SPLASH, weaponentity, nearest, force);
1072 RadiusDamage_running = 0;
1074 if(!DEATH_ISSPECIAL(deathtype))
1075 accuracy_add(attacker, DEATH_WEAPONOF(deathtype), 0, min(coredamage, stat_damagedone));
1077 return total_damage_to_creatures;
1080 float RadiusDamage(entity inflictor, entity attacker, float coredamage, float edgedamage, float rad, entity cantbe, entity mustbe, float forceintensity, int deathtype, .entity weaponentity, entity directhitentity)
1082 return RadiusDamageForSource(inflictor, (inflictor.origin + (inflictor.mins + inflictor.maxs) * 0.5), inflictor.velocity, attacker, coredamage, edgedamage, rad,
1083 cantbe, mustbe, false, forceintensity, 1, deathtype, weaponentity, directhitentity);
1086 bool Heal(entity targ, entity inflictor, float amount, float limit)
1088 if(game_stopped || (IS_CLIENT(targ) && CS(targ).killcount == FRAGS_SPECTATOR) || STAT(FROZEN, targ) || IS_DEAD(targ))
1091 bool healed = false;
1093 healed = targ.event_heal(targ, inflictor, amount, limit);
1094 // TODO: additional handling? what if the healing kills them? should this abort if healing would do so etc
1095 // TODO: healing fx!
1096 // TODO: armor healing?
1100 float Fire_IsBurning(entity e)
1102 return (time < e.fire_endtime);
1105 float Fire_AddDamage(entity e, entity o, float d, float t, float dt)
1108 float maxtime, mintime, maxdamage, mindamage, maxdps, mindps, totaldamage, totaltime;
1119 // print("adding a fire burner to ", e.classname, "\n");
1120 e.fire_burner = new(fireburner);
1121 setthink(e.fire_burner, fireburner_think);
1122 e.fire_burner.nextthink = time;
1123 e.fire_burner.owner = e;
1129 if(Fire_IsBurning(e))
1131 mintime = e.fire_endtime - time;
1132 maxtime = max(mintime, t);
1134 mindps = e.fire_damagepersec;
1135 maxdps = max(mindps, dps);
1137 if(maxtime > mintime || maxdps > mindps)
1141 // damage we have right now
1142 mindamage = mindps * mintime;
1144 // damage we want to get
1145 maxdamage = mindamage + d;
1147 // but we can't exceed maxtime * maxdps!
1148 totaldamage = min(maxdamage, maxtime * maxdps);
1152 // totaldamage = min(mindamage + d, maxtime * maxdps)
1154 // totaldamage <= maxtime * maxdps
1155 // ==> totaldamage / maxdps <= maxtime.
1157 // totaldamage / mindps = min(mindamage / mindps + d, maxtime * maxdps / mindps)
1158 // >= min(mintime, maxtime)
1159 // ==> totaldamage / maxdps >= mintime.
1162 // how long do we damage then?
1163 // at least as long as before
1164 // but, never exceed maxdps
1165 totaltime = max(mintime, totaldamage / maxdps); // always <= maxtime due to lemma
1169 // at most as long as maximum allowed
1170 // but, never below mindps
1171 totaltime = min(maxtime, totaldamage / mindps); // always >= mintime due to lemma
1173 // assuming t > mintime, dps > mindps:
1174 // we get d = t * dps = maxtime * maxdps
1175 // totaldamage = min(maxdamage, maxtime * maxdps) = min(... + d, maxtime * maxdps) = maxtime * maxdps
1176 // totaldamage / maxdps = maxtime
1177 // totaldamage / mindps > totaldamage / maxdps = maxtime
1179 // a) totaltime = max(mintime, maxtime) = maxtime
1180 // b) totaltime = min(maxtime, totaldamage / maxdps) = maxtime
1182 // assuming t <= mintime:
1183 // we get maxtime = mintime
1184 // a) totaltime = max(mintime, ...) >= mintime, also totaltime <= maxtime by the lemma, therefore totaltime = mintime = maxtime
1185 // b) totaltime = min(maxtime, ...) <= maxtime, also totaltime >= mintime by the lemma, therefore totaltime = mintime = maxtime
1187 // assuming dps <= mindps:
1188 // we get mindps = maxdps.
1189 // With this, the lemma says that mintime <= totaldamage / mindps = totaldamage / maxdps <= maxtime.
1190 // a) totaltime = max(mintime, totaldamage / maxdps) = totaldamage / maxdps
1191 // b) totaltime = min(maxtime, totaldamage / mindps) = totaldamage / maxdps
1193 e.fire_damagepersec = totaldamage / totaltime;
1194 e.fire_endtime = time + totaltime;
1195 if(totaldamage > 1.2 * mindamage)
1197 e.fire_deathtype = dt;
1198 if(e.fire_owner != o)
1201 e.fire_hitsound = false;
1204 if(accuracy_isgooddamage(o, e))
1205 accuracy_add(o, DEATH_WEAPONOF(dt), 0, max(0, totaldamage - mindamage));
1206 return max(0, totaldamage - mindamage); // can never be negative, but to make sure
1213 e.fire_damagepersec = dps;
1214 e.fire_endtime = time + t;
1215 e.fire_deathtype = dt;
1217 e.fire_hitsound = false;
1218 if(accuracy_isgooddamage(o, e))
1219 accuracy_add(o, DEATH_WEAPONOF(dt), 0, d);
1224 void Fire_ApplyDamage(entity e)
1229 if (!Fire_IsBurning(e))
1232 for(t = 0, o = e.owner; o.owner && t < 16; o = o.owner, ++t);
1233 if(IS_NOT_A_CLIENT(o))
1236 // water and slime stop fire
1238 if(e.watertype != CONTENT_LAVA)
1245 t = min(frametime, e.fire_endtime - time);
1246 d = e.fire_damagepersec * t;
1248 hi = e.fire_owner.damage_dealt;
1249 ty = e.fire_owner.typehitsound;
1250 Damage(e, e, e.fire_owner, d, e.fire_deathtype, DMG_NOWEP, e.origin, '0 0 0');
1251 if(e.fire_hitsound && e.fire_owner)
1253 e.fire_owner.damage_dealt = hi;
1254 e.fire_owner.typehitsound = ty;
1256 e.fire_hitsound = true;
1258 if(!IS_INDEPENDENT_PLAYER(e) && !STAT(FROZEN, e))
1260 IL_EACH(g_damagedbycontents, it.damagedbycontents && it != e,
1262 if(!IS_DEAD(it) && it.takedamage && !IS_INDEPENDENT_PLAYER(it))
1263 if(boxesoverlap(e.absmin, e.absmax, it.absmin, it.absmax))
1265 t = autocvar_g_balance_firetransfer_time * (e.fire_endtime - time);
1266 d = autocvar_g_balance_firetransfer_damage * e.fire_damagepersec * t;
1267 Fire_AddDamage(it, o, d, t, DEATH_FIRE.m_id);
1273 void Fire_ApplyEffect(entity e)
1275 if(Fire_IsBurning(e))
1276 e.effects |= EF_FLAME;
1278 e.effects &= ~EF_FLAME;
1281 void fireburner_think(entity this)
1283 // for players, this is done in the regular loop
1284 if(wasfreed(this.owner))
1289 Fire_ApplyEffect(this.owner);
1290 if(!Fire_IsBurning(this.owner))
1292 this.owner.fire_burner = NULL;
1296 Fire_ApplyDamage(this.owner);
1297 this.nextthink = time;