3 #include <common/constants.qh>
4 #include <common/deathtypes/all.qh>
5 #include <common/effects/all.qh>
6 #include <common/gamemodes/_mod.qh>
7 #include <common/gamemodes/rules.qh>
8 #include <common/items/_mod.qh>
9 #include <common/mapobjects/defs.qh>
10 #include <common/mapobjects/triggers.qh>
11 #include <common/mutators/mutator/buffs/buffs.qh>
12 #include <common/mutators/mutator/buffs/sv_buffs.qh>
13 #include <common/mutators/mutator/instagib/sv_instagib.qh>
14 #include <common/mutators/mutator/status_effects/_mod.qh>
15 #include <common/mutators/mutator/waypoints/waypointsprites.qh>
16 #include <common/notifications/all.qh>
17 #include <common/physics/movetypes/movetypes.qh>
18 #include <common/physics/player.qh>
19 #include <common/playerstats.qh>
20 #include <common/resources/sv_resources.qh>
21 #include <common/state.qh>
22 #include <common/teams.qh>
23 #include <common/util.qh>
24 #include <common/vehicles/all.qh>
25 #include <common/weapons/_all.qh>
26 #include <lib/csqcmodel/sv_model.qh>
27 #include <lib/warpzone/common.qh>
28 #include <server/bot/api.qh>
29 #include <server/client.qh>
30 #include <server/gamelog.qh>
31 #include <server/hook.qh>
32 #include <server/items/items.qh>
33 #include <server/main.qh>
34 #include <server/mutators/_mod.qh>
35 #include <server/scores.qh>
36 #include <server/spawnpoints.qh>
37 #include <server/teamplay.qh>
38 #include <server/weapons/accuracy.qh>
39 #include <server/weapons/csqcprojectile.qh>
40 #include <server/weapons/selection.qh>
41 #include <server/weapons/weaponsystem.qh>
42 #include <server/world.qh>
44 void UpdateFrags(entity player, int f)
46 GameRules_scoring_add_team(player, SCORE, f);
49 void GiveFrags(entity attacker, entity targ, float f, int deathtype, .entity weaponentity)
51 // TODO route through PlayerScores instead
52 if(game_stopped) return;
59 GameRules_scoring_add(attacker, SUICIDES, 1);
64 GameRules_scoring_add(attacker, TEAMKILLS, 1);
70 GameRules_scoring_add(attacker, KILLS, 1);
71 if(!warmup_stage && targ.playerid)
72 PlayerStats_GameReport_Event_Player(attacker, sprintf("kills-%d", targ.playerid), 1);
75 GameRules_scoring_add(targ, DEATHS, 1);
77 // FIXME fix the mess this is (we have REAL points now!)
78 if(MUTATOR_CALLHOOK(GiveFragsForKill, attacker, targ, f, deathtype, attacker.(weaponentity)))
81 attacker.totalfrags += f;
84 UpdateFrags(attacker, f);
87 string AppendItemcodes(string s, entity player)
89 for(int slot = 0; slot < MAX_WEAPONSLOTS; ++slot)
91 .entity weaponentity = weaponentities[slot];
92 int w = player.(weaponentity).m_weapon.m_id;
94 w = player.(weaponentity).cnt; // previous weapon
95 if(w != 0 || slot == 0)
96 s = strcat(s, ftos(w));
98 if(PHYS_INPUT_BUTTON_CHAT(player))
100 // TODO: include these codes as a flag on the item itself
101 MUTATOR_CALLHOOK(LogDeath_AppendItemCodes, player, s);
102 s = M_ARGV(1, string);
106 void LogDeath(string mode, int deathtype, entity killer, entity killed)
109 if(!autocvar_sv_eventlog)
111 s = strcat(":kill:", mode);
112 s = strcat(s, ":", ftos(killer.playerid));
113 s = strcat(s, ":", ftos(killed.playerid));
114 s = strcat(s, ":type=", Deathtype_Name(deathtype));
115 s = strcat(s, ":items=");
116 s = AppendItemcodes(s, killer);
119 s = strcat(s, ":victimitems=");
120 s = AppendItemcodes(s, killed);
125 void Obituary_SpecialDeath(
129 string s1, string s2, string s3,
130 float f1, float f2, float f3)
132 if(!DEATH_ISSPECIAL(deathtype))
134 backtrace("Obituary_SpecialDeath called without a special deathtype?\n");
138 entity deathent = REGISTRY_GET(Deathtypes, deathtype - DT_FIRST);
141 backtrace("Obituary_SpecialDeath: Could not find deathtype entity!\n");
145 if(g_cts && deathtype == DEATH_KILL.m_id)
146 return; // TODO: somehow put this in CTS gamemode file!
148 Notification death_message = (murder) ? deathent.death_msgmurder : deathent.death_msgself;
151 Send_Notification_WOCOVA(
159 Send_Notification_WOCOVA(
163 death_message.nent_msginfo,
170 float Obituary_WeaponDeath(
174 string s1, string s2, string s3,
177 Weapon death_weapon = DEATH_WEAPONOF(deathtype);
178 if (death_weapon == WEP_Null)
181 w_deathtype = deathtype;
182 Notification death_message = ((murder) ? death_weapon.wr_killmessage(death_weapon) : death_weapon.wr_suicidemessage(death_weapon));
187 Send_Notification_WOCOVA(
195 // send the info part to everyone
196 Send_Notification_WOCOVA(
200 death_message.nent_msginfo,
208 "Obituary_WeaponDeath(): ^1Deathtype ^7(%d)^1 has no notification for weapon %s!\n",
217 bool frag_centermessage_override(entity attacker, entity targ, int deathtype, int kill_count_to_attacker, int kill_count_to_target, string attacker_name)
219 if(deathtype == DEATH_FIRE.m_id)
221 Send_Notification(NOTIF_ONE, attacker, MSG_CHOICE, CHOICE_FRAG_FIRE, targ.netname, kill_count_to_attacker, (IS_BOT_CLIENT(targ) ? -1 : CS(targ).ping));
222 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));
226 return MUTATOR_CALLHOOK(FragCenterMessage, attacker, targ, deathtype, kill_count_to_attacker, kill_count_to_target);
229 void Obituary(entity attacker, entity inflictor, entity targ, int deathtype, .entity weaponentity)
232 if (!IS_PLAYER(targ)) { backtrace("Obituary called on non-player?!\n"); return; }
235 float notif_firstblood = false;
236 float kill_count_to_attacker, kill_count_to_target;
237 bool notif_anonymous = false;
238 string attacker_name = attacker.netname;
240 // Set final information for the death
241 targ.death_origin = targ.origin;
242 string deathlocation = (autocvar_notification_server_allows_location ? NearestLocation(targ.death_origin) : "");
244 // Abort now if a mutator requests it
245 if (MUTATOR_CALLHOOK(ClientObituary, inflictor, attacker, targ, deathtype, attacker.(weaponentity))) { CS(targ).killcount = 0; return; }
246 notif_anonymous = M_ARGV(5, bool);
249 attacker_name = "Anonymous player";
251 #ifdef NOTIFICATIONS_DEBUG
254 "Obituary(%s, %s, %s, %s = %d);\n",
258 Deathtype_Name(deathtype),
269 if(DEATH_ISSPECIAL(deathtype))
271 if(deathtype == DEATH_TEAMCHANGE.m_id || deathtype == DEATH_AUTOTEAMCHANGE.m_id)
273 Obituary_SpecialDeath(targ, false, deathtype, targ.netname, deathlocation, "", targ.team, 0, 0);
277 switch(DEATH_ENT(deathtype))
279 case DEATH_MIRRORDAMAGE:
281 Obituary_SpecialDeath(targ, false, deathtype, targ.netname, deathlocation, "", CS(targ).killcount, 0, 0);
284 case DEATH_HURTTRIGGER:
285 Obituary_SpecialDeath(targ, false, deathtype, targ.netname, inflictor.message, deathlocation, CS(targ).killcount, 0, 0);
289 Obituary_SpecialDeath(targ, false, deathtype, targ.netname, deathlocation, "", CS(targ).killcount, 0, 0);
295 else if (!Obituary_WeaponDeath(targ, false, deathtype, targ.netname, deathlocation, "", CS(targ).killcount, 0))
297 backtrace("SUICIDE: what the hell happened here?\n");
300 LogDeath("suicide", deathtype, targ, targ);
301 if(deathtype != DEATH_AUTOTEAMCHANGE.m_id) // special case: don't negate frags if auto switched
302 GiveFrags(attacker, targ, -1, deathtype, weaponentity);
308 else if(IS_PLAYER(attacker))
310 if(SAME_TEAM(attacker, targ))
312 LogDeath("tk", deathtype, attacker, targ);
313 GiveFrags(attacker, targ, -1, deathtype, weaponentity);
315 CS(attacker).killcount = 0;
317 Send_Notification(NOTIF_ONE, attacker, MSG_CENTER, CENTER_DEATH_TEAMKILL_FRAG, targ.netname);
318 Send_Notification(NOTIF_ONE, targ, MSG_CENTER, CENTER_DEATH_TEAMKILL_FRAGGED, attacker_name);
319 Send_Notification(NOTIF_ALL, NULL, MSG_INFO, APP_TEAM_NUM(targ.team, INFO_DEATH_TEAMKILL), targ.netname, attacker_name, deathlocation, CS(targ).killcount);
321 // In this case, the death message will ALWAYS be "foo was betrayed by bar"
322 // No need for specific death/weapon messages...
326 LogDeath("frag", deathtype, attacker, targ);
327 GiveFrags(attacker, targ, 1, deathtype, weaponentity);
329 CS(attacker).taunt_soundtime = time + 1;
330 CS(attacker).killcount = CS(attacker).killcount + 1;
332 attacker.killsound += 1;
334 // TODO: improve SPREE_ITEM and KILL_SPREE_LIST
335 // these 2 macros are spread over multiple files
336 #define SPREE_ITEM(counta,countb,center,normal,gentle) \
338 Send_Notification(NOTIF_ONE, attacker, MSG_ANNCE, ANNCE_KILLSTREAK_##countb); \
340 PlayerStats_GameReport_Event_Player(attacker, PLAYERSTATS_ACHIEVEMENT_KILL_SPREE_##counta, 1); \
343 switch(CS(attacker).killcount)
350 if(!warmup_stage && !checkrules_firstblood)
352 checkrules_firstblood = true;
353 notif_firstblood = true; // modify the current messages so that they too show firstblood information
354 PlayerStats_GameReport_Event_Player(attacker, PLAYERSTATS_ACHIEVEMENT_FIRSTBLOOD, 1);
355 PlayerStats_GameReport_Event_Player(targ, PLAYERSTATS_ACHIEVEMENT_FIRSTVICTIM, 1);
357 // tell spree_inf and spree_cen that this is a first-blood and first-victim event
358 kill_count_to_attacker = -1;
359 kill_count_to_target = -2;
363 kill_count_to_attacker = CS(attacker).killcount;
364 kill_count_to_target = 0;
375 kill_count_to_attacker,
376 (IS_BOT_CLIENT(targ) ? -1 : CS(targ).ping)
384 kill_count_to_target,
385 GetResource(attacker, RES_HEALTH),
386 GetResource(attacker, RES_ARMOR),
387 (IS_BOT_CLIENT(attacker) ? -1 : CS(attacker).ping)
390 else if(!frag_centermessage_override(attacker, targ, deathtype, kill_count_to_attacker, kill_count_to_target, attacker_name))
398 kill_count_to_attacker,
399 (IS_BOT_CLIENT(targ) ? -1 : CS(targ).ping)
407 kill_count_to_target,
408 GetResource(attacker, RES_HEALTH),
409 GetResource(attacker, RES_ARMOR),
410 (IS_BOT_CLIENT(attacker) ? -1 : CS(attacker).ping)
415 if(deathtype == DEATH_BUFF.m_id)
416 f3 = buff_FirstFromFlags(attacker).m_id;
418 if (!Obituary_WeaponDeath(targ, true, deathtype, targ.netname, attacker_name, deathlocation, CS(targ).killcount, kill_count_to_attacker))
419 Obituary_SpecialDeath(targ, true, deathtype, targ.netname, attacker_name, deathlocation, CS(targ).killcount, kill_count_to_attacker, f3);
428 switch(DEATH_ENT(deathtype))
430 // For now, we're just forcing HURTTRIGGER to behave as "DEATH_VOID" and giving it no special options...
431 // Later on you will only be able to make custom messages using DEATH_CUSTOM,
432 // and there will be a REAL DEATH_VOID implementation which mappers will use.
433 case DEATH_HURTTRIGGER:
435 Obituary_SpecialDeath(targ, false, deathtype,
447 Obituary_SpecialDeath(targ, false, deathtype,
449 ((strstrofs(deathmessage, "%", 0) < 0) ? strcat("%s ", deathmessage) : deathmessage),
459 Obituary_SpecialDeath(targ, false, deathtype, targ.netname, deathlocation, "", CS(targ).killcount, 0, 0);
464 LogDeath("accident", deathtype, targ, targ);
465 GiveFrags(targ, targ, -1, deathtype, weaponentity);
467 if(GameRules_scoring_add(targ, SCORE, 0) == -5)
469 Send_Notification(NOTIF_ONE, targ, MSG_ANNCE, ANNCE_ACHIEVEMENT_BOTLIKE);
472 PlayerStats_GameReport_Event_Player(attacker, PLAYERSTATS_ACHIEVEMENT_BOTLIKE, 1);
477 // reset target kill count
478 CS(targ).killcount = 0;
481 void Ice_Think(entity this)
483 if(!STAT(FROZEN, this.owner) || this.owner.iceblock != this)
488 vector ice_org = this.owner.origin - '0 0 16';
489 if (this.origin != ice_org)
490 setorigin(this, ice_org);
491 this.nextthink = time;
494 void Freeze(entity targ, float revivespeed, int frozen_type, bool show_waypoint)
496 if(!IS_PLAYER(targ) && !IS_MONSTER(targ)) // TODO: only specified entities can be freezed
499 if(STAT(FROZEN, targ))
502 float targ_maxhealth = ((IS_MONSTER(targ)) ? targ.max_health : start_health);
504 STAT(FROZEN, targ) = frozen_type;
505 STAT(REVIVE_PROGRESS, targ) = ((frozen_type == FROZEN_TEMP_DYING) ? 1 : 0);
506 SetResource(targ, RES_HEALTH, ((frozen_type == FROZEN_TEMP_DYING) ? targ_maxhealth : 1));
507 targ.revive_speed = revivespeed;
509 IL_REMOVE(g_bot_targets, targ);
510 targ.bot_attack = false;
511 targ.freeze_time = time;
513 entity ice = new(ice);
515 ice.scale = targ.scale;
516 // set_movetype(ice, MOVETYPE_FOLLOW) would rotate the ice model with the player
517 setthink(ice, Ice_Think);
518 ice.nextthink = time;
519 ice.frame = floor(random() * 21); // ice model has 20 different looking frames
520 setmodel(ice, MDL_ICE);
522 ice.colormod = Team_ColorRGB(targ.team);
523 ice.glowmod = ice.colormod;
525 targ.revival_time = 0;
529 RemoveGrapplingHooks(targ);
531 FOREACH_CLIENT(IS_PLAYER(it),
533 for(int slot = 0; slot < MAX_WEAPONSLOTS; ++slot)
535 .entity weaponentity = weaponentities[slot];
536 if(it.(weaponentity).hook.aiment == targ)
537 RemoveHook(it.(weaponentity).hook);
542 if(MUTATOR_CALLHOOK(Freeze, targ, revivespeed, frozen_type) || show_waypoint)
543 WaypointSprite_Spawn(WP_Frozen, 0, 0, targ, '0 0 64', NULL, targ.team, targ, waypointsprite_attached, true, RADARICON_WAYPOINT);
546 void Unfreeze(entity targ, bool reset_health)
548 if(!STAT(FROZEN, targ))
551 if (reset_health && STAT(FROZEN, targ) != FROZEN_TEMP_DYING)
552 SetResource(targ, RES_HEALTH, ((IS_PLAYER(targ)) ? start_health : targ.max_health));
554 targ.pauseregen_finished = time + autocvar_g_balance_pause_health_regen;
556 STAT(FROZEN, targ) = 0;
557 STAT(REVIVE_PROGRESS, targ) = 0;
558 targ.revival_time = time;
560 IL_PUSH(g_bot_targets, targ);
561 targ.bot_attack = true;
563 WaypointSprite_Kill(targ.waypointsprite_attached);
565 FOREACH_CLIENT(IS_PLAYER(it),
567 for(int slot = 0; slot < MAX_WEAPONSLOTS; ++slot)
569 .entity weaponentity = weaponentities[slot];
570 if(it.(weaponentity).hook.aiment == targ)
571 RemoveHook(it.(weaponentity).hook);
575 // remove the ice block
577 delete(targ.iceblock);
578 targ.iceblock = NULL;
580 MUTATOR_CALLHOOK(Unfreeze, targ);
583 void Damage(entity targ, entity inflictor, entity attacker, float damage, int deathtype, .entity weaponentity, vector hitloc, vector force)
585 float complainteamdamage = 0;
586 float mirrordamage = 0;
587 float mirrorforce = 0;
589 if (game_stopped || (IS_CLIENT(targ) && CS(targ).killcount == FRAGS_SPECTATOR))
592 entity attacker_save = attacker;
594 // special rule: gravity bombs and sound-based attacks do not affect team mates (other than for disconnecting the hook)
595 if(DEATH_ISWEAPON(deathtype, WEP_HOOK) || (deathtype & HITTYPE_SOUND))
597 if(IS_PLAYER(targ) && SAME_TEAM(targ, attacker))
603 if(deathtype == DEATH_KILL.m_id || deathtype == DEATH_TEAMCHANGE.m_id || deathtype == DEATH_AUTOTEAMCHANGE.m_id)
605 // exit the vehicle before killing (fixes a crash)
606 if(IS_PLAYER(targ) && targ.vehicle)
607 vehicles_exit(targ.vehicle, VHEF_RELEASE);
609 // These are ALWAYS lethal
610 // No damage modification here
611 // Instead, prepare the victim for his death...
612 if(deathtype == DEATH_TEAMCHANGE.m_id || deathtype == DEATH_AUTOTEAMCHANGE.m_id)
614 SetResourceExplicit(targ, RES_ARMOR, 0);
615 SetResourceExplicit(targ, RES_HEALTH, 0.9); // this is < 1
617 StatusEffects_remove(STATUSEFFECT_SpawnShield, targ, STATUSEFFECT_REMOVE_CLEAR);
618 targ.flags -= targ.flags & FL_GODMODE;
621 else if(deathtype == DEATH_MIRRORDAMAGE.m_id || deathtype == DEATH_NOAMMO.m_id)
627 // nullify damage if teamplay is on
628 if(deathtype != DEATH_TELEFRAG.m_id)
629 if(IS_PLAYER(attacker))
631 // avoid dealing damage or force to other independent players
632 // and avoid dealing damage or force to things owned by other independent players
633 if((IS_PLAYER(targ) && targ != attacker && (IS_INDEPENDENT_PLAYER(attacker) || IS_INDEPENDENT_PLAYER(targ))) ||
634 (targ.realowner && IS_INDEPENDENT_PLAYER(targ.realowner) && attacker != targ.realowner))
639 else if(!STAT(FROZEN, targ) && SAME_TEAM(attacker, targ))
641 if(autocvar_teamplay_mode == 1)
643 else if(attacker != targ)
645 if(autocvar_teamplay_mode == 2)
647 if(IS_PLAYER(targ) && !IS_DEAD(targ))
649 attacker.dmg_team = attacker.dmg_team + damage;
650 complainteamdamage = attacker.dmg_team - autocvar_g_teamdamage_threshold;
653 else if(autocvar_teamplay_mode == 3)
655 else if(autocvar_teamplay_mode == 4)
657 if(IS_PLAYER(targ) && !IS_DEAD(targ))
659 attacker.dmg_team = attacker.dmg_team + damage;
660 complainteamdamage = attacker.dmg_team - autocvar_g_teamdamage_threshold;
661 if(complainteamdamage > 0)
662 mirrordamage = autocvar_g_mirrordamage * complainteamdamage;
663 mirrorforce = autocvar_g_mirrordamage * vlen(force);
664 damage = autocvar_g_friendlyfire * damage;
665 // mirrordamage will be used LATER
667 if(autocvar_g_mirrordamage_virtual)
669 vector v = healtharmor_applydamage(GetResource(attacker, RES_ARMOR), autocvar_g_balance_armor_blockpercent, deathtype, mirrordamage);
670 attacker.dmg_take += v.x;
671 attacker.dmg_save += v.y;
672 attacker.dmg_inflictor = inflictor;
677 if(autocvar_g_friendlyfire_virtual)
679 vector v = healtharmor_applydamage(GetResource(targ, RES_ARMOR), autocvar_g_balance_armor_blockpercent, deathtype, damage);
680 targ.dmg_take += v.x;
681 targ.dmg_save += v.y;
682 targ.dmg_inflictor = inflictor;
684 if(!autocvar_g_friendlyfire_virtual_force)
688 else if(!targ.canteamdamage)
695 if (!DEATH_ISSPECIAL(deathtype))
697 damage *= autocvar_g_weapondamagefactor;
698 mirrordamage *= autocvar_g_weapondamagefactor;
699 complainteamdamage *= autocvar_g_weapondamagefactor;
700 force = force * autocvar_g_weaponforcefactor;
701 mirrorforce *= autocvar_g_weaponforcefactor;
704 // should this be changed at all? If so, in what way?
705 MUTATOR_CALLHOOK(Damage_Calculate, inflictor, attacker, targ, deathtype, damage, mirrordamage, force, attacker.(weaponentity));
706 damage = M_ARGV(4, float);
707 mirrordamage = M_ARGV(5, float);
708 force = M_ARGV(6, vector);
710 if(IS_PLAYER(targ) && damage > 0 && attacker)
712 for(int slot = 0; slot < MAX_WEAPONSLOTS; ++slot)
714 .entity went = weaponentities[slot];
715 if(targ.(went).hook && targ.(went).hook.aiment == attacker)
716 RemoveHook(targ.(went).hook);
720 if(STAT(FROZEN, targ) && !ITEM_DAMAGE_NEEDKILL(deathtype)
721 && deathtype != DEATH_TEAMCHANGE.m_id && deathtype != DEATH_AUTOTEAMCHANGE.m_id)
723 if(autocvar_g_frozen_revive_falldamage > 0 && deathtype == DEATH_FALL.m_id && damage >= autocvar_g_frozen_revive_falldamage)
725 Unfreeze(targ, false);
726 SetResource(targ, RES_HEALTH, autocvar_g_frozen_revive_falldamage_health);
727 Send_Effect(EFFECT_ICEORGLASS, targ.origin, '0 0 0', 3);
728 Send_Notification(NOTIF_ALL, NULL, MSG_INFO, INFO_FREEZETAG_REVIVED_FALL, targ.netname);
729 Send_Notification(NOTIF_ONE, targ, MSG_CENTER, CENTER_FREEZETAG_REVIVE_SELF);
733 force *= autocvar_g_frozen_force;
736 if(IS_PLAYER(targ) && STAT(FROZEN, targ)
737 && ITEM_DAMAGE_NEEDKILL(deathtype) && !autocvar_g_frozen_damage_trigger)
739 Send_Effect(EFFECT_TELEPORT, targ.origin, '0 0 0', 1);
741 entity spot = SelectSpawnPoint(targ, false);
745 targ.deadflag = DEAD_NO;
747 targ.angles = spot.angles;
750 targ.effects |= EF_TELEPORT_BIT;
752 targ.angles_z = 0; // never spawn tilted even if the spot says to
753 targ.fixangle = true; // turn this way immediately
754 targ.velocity = '0 0 0';
755 targ.avelocity = '0 0 0';
756 targ.punchangle = '0 0 0';
757 targ.punchvector = '0 0 0';
758 targ.oldvelocity = targ.velocity;
760 targ.spawnorigin = spot.origin;
761 setorigin(targ, spot.origin + '0 0 1' * (1 - targ.mins.z - 24));
762 // don't reset back to last position, even if new position is stuck in solid
763 targ.oldorigin = targ.origin;
765 Send_Effect(EFFECT_TELEPORT, targ.origin, '0 0 0', 1);
769 if (targ == attacker)
770 damage = damage * autocvar_g_balance_selfdamagepercent; // Partial damage if the attacker hits himself
775 if(deathtype != DEATH_BUFF.m_id)
776 if(targ.takedamage == DAMAGE_AIM)
780 if(IS_VEHICLE(targ) && targ.owner)
785 if(IS_PLAYER(victim) || (IS_TURRET(victim) && victim.active == ACTIVE_ACTIVE) || IS_MONSTER(victim) || MUTATOR_CALLHOOK(PlayHitsound, victim, attacker))
787 if (DIFF_TEAM(victim, attacker))
791 if(deathtype != DEATH_FIRE.m_id)
793 if(PHYS_INPUT_BUTTON_CHAT(victim))
794 attacker.typehitsound += 1;
796 attacker.hitsound_damage_dealt += damage;
799 impressive_hits += 1;
801 if (!DEATH_ISSPECIAL(deathtype))
803 if(IS_PLAYER(targ)) // don't do this for vehicles
809 else if (IS_PLAYER(attacker) && !STAT(FROZEN, victim)) // same team
811 if (deathtype != DEATH_FIRE.m_id)
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) || !StatusEffects_active(STATUSEFFECT_SpawnShield, targ) || 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);
845 else if(targ.move_movetype != MOVETYPE_NOCLIP)
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 // Returns total damage applies to creatures
869 float RadiusDamageForSource (entity inflictor, vector inflictororigin, vector inflictorvelocity, entity attacker, float coredamage, float edgedamage, float rad, entity cantbe, entity mustbe,
870 float inflictorselfdamage, float forceintensity, float forcezscale, int deathtype, .entity weaponentity, entity directhitentity)
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 if (rad < 0) rad = 0;
889 RadiusDamage_running = 1;
891 tfloordmg = autocvar_g_throughfloor_damage;
892 tfloorforce = autocvar_g_throughfloor_force;
894 total_damage_to_creatures = 0;
896 if(deathtype != (WEP_HOOK.m_id | HITTYPE_SECONDARY | HITTYPE_BOUNCE)) // only send gravity bomb damage once
897 if(!(deathtype & HITTYPE_SOUND)) // do not send radial sound damage (bandwidth hog)
899 force = inflictorvelocity;
903 force = normalize(force);
904 if(forceintensity >= 0)
905 Damage_DamageInfo(inflictororigin, coredamage, edgedamage, rad, forceintensity * force, deathtype, 0, attacker);
907 Damage_DamageInfo(inflictororigin, coredamage, edgedamage, -rad, (-forceintensity) * force, deathtype, 0, attacker);
912 targ = WarpZone_FindRadius (inflictororigin, rad + MAX_DAMAGEEXTRARADIUS, false);
916 if ((targ != inflictor) || inflictorselfdamage)
917 if (((cantbe != targ) && !mustbe) || (mustbe == targ))
920 // measure distance from nearest point on target (not origin)
921 // to nearest point on inflictor (not origin)
922 vector nearest = targ.WarpZone_findradius_nearest;
923 vector inflictornearest = NearestPointOnBoundingBox(
924 inflictororigin - (inflictor.maxs - inflictor.mins) * 0.5,
925 inflictororigin + (inflictor.maxs - inflictor.mins) * 0.5,
927 vector diff = inflictornearest - nearest;
929 // round up a little on the damage to ensure full damage on impacts
930 // and turn the distance into a fraction of the radius
931 float dist = max(0, vlen(diff) - bound(MIN_DAMAGEEXTRARADIUS, targ.damageextraradius, MAX_DAMAGEEXTRARADIUS));
934 float f = (rad > 0) ? 1 - (dist / rad) : 1;
935 // at this point f can't be < 0 or > 1
936 float finaldmg = coredamage * f + edgedamage * (1 - f);
942 vector myblastorigin;
945 myblastorigin = WarpZone_TransformOrigin(targ, inflictororigin);
947 // if it's a player, use the view origin as reference
948 center = CENTER_OR_VIEWOFS(targ);
950 force = normalize(center - myblastorigin);
951 force = force * (finaldmg / coredamage) * forceintensity;
954 // apply special scaling along the z axis if set
955 // NOTE: 0 value is not allowed for compatibility, in the case of weapon cvars not being set
957 force.z *= forcezscale;
959 if(targ != directhitentity)
964 float mininv_f, mininv_d;
966 // test line of sight to multiple positions on box,
967 // and do damage if any of them hit
970 // we know: max stddev of hitratio = 1 / (2 * sqrt(n))
971 // so for a given max stddev:
972 // n = (1 / (2 * max stddev of hitratio))^2
974 mininv_d = (finaldmg * (1-tfloordmg)) / autocvar_g_throughfloor_damage_max_stddev;
975 mininv_f = (vlen(force) * (1-tfloorforce)) / autocvar_g_throughfloor_force_max_stddev;
977 if(autocvar_g_throughfloor_debug)
978 LOG_INFOF("THROUGHFLOOR: D=%f F=%f max(dD)=1/%f max(dF)=1/%f", finaldmg, vlen(force), mininv_d, mininv_f);
981 total = 0.25 * (max(mininv_f, mininv_d) ** 2);
983 if(autocvar_g_throughfloor_debug)
984 LOG_INFOF(" steps=%f", total);
988 total = ceil(bound(autocvar_g_throughfloor_min_steps_player, total, autocvar_g_throughfloor_max_steps_player));
990 total = ceil(bound(autocvar_g_throughfloor_min_steps_other, total, autocvar_g_throughfloor_max_steps_other));
992 if(autocvar_g_throughfloor_debug)
993 LOG_INFOF(" steps=%f dD=%f dF=%f", total, finaldmg * (1-tfloordmg) / (2 * sqrt(total)), vlen(force) * (1-tfloorforce) / (2 * sqrt(total)));
995 for(c = 0; c < total; ++c)
997 //traceline(targ.WarpZone_findradius_findorigin, nearest, MOVE_NOMONSTERS, inflictor);
998 WarpZone_TraceLine(inflictororigin, WarpZone_UnTransformOrigin(targ, nearest), MOVE_NOMONSTERS, inflictor);
999 if (trace_fraction == 1 || trace_ent == targ)
1003 hitloc = hitloc + nearest;
1007 nearest.x = targ.origin.x + targ.mins.x + random() * targ.size.x;
1008 nearest.y = targ.origin.y + targ.mins.y + random() * targ.size.y;
1009 nearest.z = targ.origin.z + targ.mins.z + random() * targ.size.z;
1012 nearest = hitloc * (1 / max(1, hits));
1013 hitratio = (hits / total);
1014 a = bound(0, tfloordmg + (1-tfloordmg) * hitratio, 1);
1015 finaldmg = finaldmg * a;
1016 a = bound(0, tfloorforce + (1-tfloorforce) * hitratio, 1);
1019 if(autocvar_g_throughfloor_debug)
1020 LOG_INFOF(" D=%f F=%f", finaldmg, vlen(force));
1023 //if (targ == attacker)
1025 // print("hits ", ftos(hits), " / ", ftos(total));
1026 // print(" finaldmg ", ftos(finaldmg), " force ", vtos(force));
1027 // print(" (", ftos(a), ")\n");
1029 if(finaldmg || force)
1033 total_damage_to_creatures += finaldmg;
1035 if(accuracy_isgooddamage(attacker, targ))
1036 stat_damagedone += finaldmg;
1039 if(targ == directhitentity || DEATH_ISSPECIAL(deathtype))
1040 Damage(targ, inflictor, attacker, finaldmg, deathtype, weaponentity, nearest, force);
1042 Damage(targ, inflictor, attacker, finaldmg, deathtype | HITTYPE_SPLASH, weaponentity, nearest, force);
1050 RadiusDamage_running = 0;
1052 if(!DEATH_ISSPECIAL(deathtype))
1053 accuracy_add(attacker, DEATH_WEAPONOF(deathtype), 0, min(coredamage, stat_damagedone));
1055 return total_damage_to_creatures;
1058 float RadiusDamage(entity inflictor, entity attacker, float coredamage, float edgedamage, float rad, entity cantbe, entity mustbe, float forceintensity, int deathtype, .entity weaponentity, entity directhitentity)
1060 return RadiusDamageForSource(inflictor, (inflictor.origin + (inflictor.mins + inflictor.maxs) * 0.5), inflictor.velocity, attacker, coredamage, edgedamage, rad,
1061 cantbe, mustbe, false, forceintensity, 1, deathtype, weaponentity, directhitentity);
1064 bool Heal(entity targ, entity inflictor, float amount, float limit)
1066 if(game_stopped || (IS_CLIENT(targ) && CS(targ).killcount == FRAGS_SPECTATOR) || STAT(FROZEN, targ) || IS_DEAD(targ))
1069 bool healed = false;
1071 healed = targ.event_heal(targ, inflictor, amount, limit);
1072 // TODO: additional handling? what if the healing kills them? should this abort if healing would do so etc
1073 // TODO: healing fx!
1074 // TODO: armor healing?
1078 float Fire_AddDamage(entity e, entity o, float d, float t, float dt)
1081 float maxtime, mintime, maxdamage, mindamage, maxdps, mindps, totaldamage, totaltime;
1091 if(StatusEffects_active(STATUSEFFECT_Burning, e))
1093 float fireendtime = StatusEffects_gettime(STATUSEFFECT_Burning, e);
1095 mintime = fireendtime - time;
1096 maxtime = max(mintime, t);
1098 mindps = e.fire_damagepersec;
1099 maxdps = max(mindps, dps);
1101 if(maxtime > mintime || maxdps > mindps)
1105 // damage we have right now
1106 mindamage = mindps * mintime;
1108 // damage we want to get
1109 maxdamage = mindamage + d;
1111 // but we can't exceed maxtime * maxdps!
1112 totaldamage = min(maxdamage, maxtime * maxdps);
1116 // totaldamage = min(mindamage + d, maxtime * maxdps)
1118 // totaldamage <= maxtime * maxdps
1119 // ==> totaldamage / maxdps <= maxtime.
1121 // totaldamage / mindps = min(mindamage / mindps + d, maxtime * maxdps / mindps)
1122 // >= min(mintime, maxtime)
1123 // ==> totaldamage / maxdps >= mintime.
1126 // how long do we damage then?
1127 // at least as long as before
1128 // but, never exceed maxdps
1129 totaltime = max(mintime, totaldamage / maxdps); // always <= maxtime due to lemma
1133 // at most as long as maximum allowed
1134 // but, never below mindps
1135 totaltime = min(maxtime, totaldamage / mindps); // always >= mintime due to lemma
1137 // assuming t > mintime, dps > mindps:
1138 // we get d = t * dps = maxtime * maxdps
1139 // totaldamage = min(maxdamage, maxtime * maxdps) = min(... + d, maxtime * maxdps) = maxtime * maxdps
1140 // totaldamage / maxdps = maxtime
1141 // totaldamage / mindps > totaldamage / maxdps = maxtime
1143 // a) totaltime = max(mintime, maxtime) = maxtime
1144 // b) totaltime = min(maxtime, totaldamage / maxdps) = maxtime
1146 // assuming t <= mintime:
1147 // we get maxtime = mintime
1148 // a) totaltime = max(mintime, ...) >= mintime, also totaltime <= maxtime by the lemma, therefore totaltime = mintime = maxtime
1149 // b) totaltime = min(maxtime, ...) <= maxtime, also totaltime >= mintime by the lemma, therefore totaltime = mintime = maxtime
1151 // assuming dps <= mindps:
1152 // we get mindps = maxdps.
1153 // With this, the lemma says that mintime <= totaldamage / mindps = totaldamage / maxdps <= maxtime.
1154 // a) totaltime = max(mintime, totaldamage / maxdps) = totaldamage / maxdps
1155 // b) totaltime = min(maxtime, totaldamage / mindps) = totaldamage / maxdps
1157 e.fire_damagepersec = totaldamage / totaltime;
1158 StatusEffects_apply(STATUSEFFECT_Burning, e, time + totaltime, 0);
1159 if(totaldamage > 1.2 * mindamage)
1161 e.fire_deathtype = dt;
1162 if(e.fire_owner != o)
1165 e.fire_hitsound = false;
1168 if(accuracy_isgooddamage(o, e))
1169 accuracy_add(o, DEATH_WEAPONOF(dt), 0, max(0, totaldamage - mindamage));
1170 return max(0, totaldamage - mindamage); // can never be negative, but to make sure
1177 e.fire_damagepersec = dps;
1178 StatusEffects_apply(STATUSEFFECT_Burning, e, time + t, 0);
1179 e.fire_deathtype = dt;
1181 e.fire_hitsound = false;
1182 if(accuracy_isgooddamage(o, e))
1183 accuracy_add(o, DEATH_WEAPONOF(dt), 0, d);
1188 void Fire_ApplyDamage(entity e)
1193 for(t = 0, o = e.owner; o.owner && t < 16; o = o.owner, ++t);
1194 if(IS_NOT_A_CLIENT(o))
1197 float fireendtime = StatusEffects_gettime(STATUSEFFECT_Burning, e);
1198 t = min(frametime, fireendtime - time);
1199 d = e.fire_damagepersec * t;
1201 hi = e.fire_owner.hitsound_damage_dealt;
1202 ty = e.fire_owner.typehitsound;
1203 Damage(e, e, e.fire_owner, d, e.fire_deathtype, DMG_NOWEP, e.origin, '0 0 0');
1204 if(e.fire_hitsound && e.fire_owner)
1206 e.fire_owner.hitsound_damage_dealt = hi;
1207 e.fire_owner.typehitsound = ty;
1209 e.fire_hitsound = true;
1211 if(!IS_INDEPENDENT_PLAYER(e) && !STAT(FROZEN, e))
1213 IL_EACH(g_damagedbycontents, it.damagedbycontents && it != e,
1215 if(!IS_DEAD(it) && it.takedamage && !IS_INDEPENDENT_PLAYER(it))
1216 if(boxesoverlap(e.absmin, e.absmax, it.absmin, it.absmax))
1218 t = autocvar_g_balance_firetransfer_time * (fireendtime - time);
1219 d = autocvar_g_balance_firetransfer_damage * e.fire_damagepersec * t;
1220 Fire_AddDamage(it, o, d, t, DEATH_FIRE.m_id);