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 if(IS_PLAYER(targ) && targ != attacker && (IS_INDEPENDENT_PLAYER(attacker) || IS_INDEPENDENT_PLAYER(targ)))
637 // avoid dealing damage or force to things owned by other independent players
639 if(IS_INDEPENDENT_PLAYER(targ.realowner) && attacker != targ.realowner)
644 else if(!STAT(FROZEN, targ) && SAME_TEAM(attacker, targ))
646 if(autocvar_teamplay_mode == 1)
648 else if(attacker != targ)
650 if(autocvar_teamplay_mode == 2)
652 if(IS_PLAYER(targ) && !IS_DEAD(targ))
654 attacker.dmg_team = attacker.dmg_team + damage;
655 complainteamdamage = attacker.dmg_team - autocvar_g_teamdamage_threshold;
658 else if(autocvar_teamplay_mode == 3)
660 else if(autocvar_teamplay_mode == 4)
662 if(IS_PLAYER(targ) && !IS_DEAD(targ))
664 attacker.dmg_team = attacker.dmg_team + damage;
665 complainteamdamage = attacker.dmg_team - autocvar_g_teamdamage_threshold;
666 if(complainteamdamage > 0)
667 mirrordamage = autocvar_g_mirrordamage * complainteamdamage;
668 mirrorforce = autocvar_g_mirrordamage * vlen(force);
669 damage = autocvar_g_friendlyfire * damage;
670 // mirrordamage will be used LATER
672 if(autocvar_g_mirrordamage_virtual)
674 vector v = healtharmor_applydamage(GetResource(attacker, RES_ARMOR), autocvar_g_balance_armor_blockpercent, deathtype, mirrordamage);
675 attacker.dmg_take += v.x;
676 attacker.dmg_save += v.y;
677 attacker.dmg_inflictor = inflictor;
682 if(autocvar_g_friendlyfire_virtual)
684 vector v = healtharmor_applydamage(GetResource(targ, RES_ARMOR), autocvar_g_balance_armor_blockpercent, deathtype, damage);
685 targ.dmg_take += v.x;
686 targ.dmg_save += v.y;
687 targ.dmg_inflictor = inflictor;
689 if(!autocvar_g_friendlyfire_virtual_force)
693 else if(!targ.canteamdamage)
700 if (!DEATH_ISSPECIAL(deathtype))
702 damage *= autocvar_g_weapondamagefactor;
703 mirrordamage *= autocvar_g_weapondamagefactor;
704 complainteamdamage *= autocvar_g_weapondamagefactor;
705 force = force * autocvar_g_weaponforcefactor;
706 mirrorforce *= autocvar_g_weaponforcefactor;
709 // should this be changed at all? If so, in what way?
710 MUTATOR_CALLHOOK(Damage_Calculate, inflictor, attacker, targ, deathtype, damage, mirrordamage, force, attacker.(weaponentity));
711 damage = M_ARGV(4, float);
712 mirrordamage = M_ARGV(5, float);
713 force = M_ARGV(6, vector);
715 if(IS_PLAYER(targ) && damage > 0 && attacker)
717 for(int slot = 0; slot < MAX_WEAPONSLOTS; ++slot)
719 .entity went = weaponentities[slot];
720 if(targ.(went).hook && targ.(went).hook.aiment == attacker)
721 RemoveHook(targ.(went).hook);
725 if(STAT(FROZEN, targ) && !ITEM_DAMAGE_NEEDKILL(deathtype)
726 && deathtype != DEATH_TEAMCHANGE.m_id && deathtype != DEATH_AUTOTEAMCHANGE.m_id)
728 if(autocvar_g_frozen_revive_falldamage > 0 && deathtype == DEATH_FALL.m_id && damage >= autocvar_g_frozen_revive_falldamage)
730 Unfreeze(targ, false);
731 SetResource(targ, RES_HEALTH, autocvar_g_frozen_revive_falldamage_health);
732 Send_Effect(EFFECT_ICEORGLASS, targ.origin, '0 0 0', 3);
733 Send_Notification(NOTIF_ALL, NULL, MSG_INFO, INFO_FREEZETAG_REVIVED_FALL, targ.netname);
734 Send_Notification(NOTIF_ONE, targ, MSG_CENTER, CENTER_FREEZETAG_REVIVE_SELF);
738 force *= autocvar_g_frozen_force;
741 if(IS_PLAYER(targ) && STAT(FROZEN, targ)
742 && ITEM_DAMAGE_NEEDKILL(deathtype) && !autocvar_g_frozen_damage_trigger)
744 Send_Effect(EFFECT_TELEPORT, targ.origin, '0 0 0', 1);
746 entity spot = SelectSpawnPoint(targ, false);
750 targ.deadflag = DEAD_NO;
752 targ.angles = spot.angles;
755 targ.effects |= EF_TELEPORT_BIT;
757 targ.angles_z = 0; // never spawn tilted even if the spot says to
758 targ.fixangle = true; // turn this way immediately
759 targ.velocity = '0 0 0';
760 targ.avelocity = '0 0 0';
761 targ.punchangle = '0 0 0';
762 targ.punchvector = '0 0 0';
763 targ.oldvelocity = targ.velocity;
765 targ.spawnorigin = spot.origin;
766 setorigin(targ, spot.origin + '0 0 1' * (1 - targ.mins.z - 24));
767 // don't reset back to last position, even if new position is stuck in solid
768 targ.oldorigin = targ.origin;
770 Send_Effect(EFFECT_TELEPORT, targ.origin, '0 0 0', 1);
774 if (targ == attacker)
775 damage = damage * autocvar_g_balance_selfdamagepercent; // Partial damage if the attacker hits himself
780 if(deathtype != DEATH_BUFF.m_id)
781 if(targ.takedamage == DAMAGE_AIM)
785 if(IS_VEHICLE(targ) && targ.owner)
790 if(IS_PLAYER(victim) || (IS_TURRET(victim) && victim.active == ACTIVE_ACTIVE) || IS_MONSTER(victim) || MUTATOR_CALLHOOK(PlayHitsound, victim, attacker))
792 if (DIFF_TEAM(victim, attacker))
796 if(deathtype != DEATH_FIRE.m_id)
798 if(PHYS_INPUT_BUTTON_CHAT(victim))
799 attacker.typehitsound += 1;
801 attacker.hitsound_damage_dealt += damage;
804 impressive_hits += 1;
806 if (!DEATH_ISSPECIAL(deathtype))
808 if(IS_PLAYER(targ)) // don't do this for vehicles
814 else if (IS_PLAYER(attacker) && !STAT(FROZEN, victim)) // same team
816 if (deathtype != DEATH_FIRE.m_id)
818 attacker.typehitsound += 1;
820 if(complainteamdamage > 0)
821 if(time > CS(attacker).teamkill_complain)
823 CS(attacker).teamkill_complain = time + 5;
824 CS(attacker).teamkill_soundtime = time + 0.4;
825 CS(attacker).teamkill_soundsource = targ;
833 if (targ.damageforcescale)
835 if (!IS_PLAYER(targ) || !StatusEffects_active(STATUSEFFECT_SpawnShield, targ) || targ == attacker)
837 vector farce = damage_explosion_calcpush(targ.damageforcescale * force, targ.velocity, autocvar_g_balance_damagepush_speedfactor);
838 if(targ.move_movetype == MOVETYPE_PHYSICS)
840 entity farcent = new(farce);
841 farcent.enemy = targ;
842 farcent.movedir = farce * 10;
844 farcent.movedir = farcent.movedir * targ.mass;
845 farcent.origin = hitloc;
846 farcent.forcetype = FORCETYPE_FORCEATPOS;
847 farcent.nextthink = time + 0.1;
848 setthink(farcent, SUB_Remove);
850 else if(targ.move_movetype != MOVETYPE_NOCLIP)
852 targ.velocity = targ.velocity + farce;
854 UNSET_ONGROUND(targ);
855 UpdateCSQCProjectile(targ);
858 if (damage != 0 || (targ.damageforcescale && force))
859 if (targ.event_damage)
860 targ.event_damage (targ, inflictor, attacker, damage, deathtype, weaponentity, hitloc, force);
862 // apply mirror damage if any
863 if(!autocvar_g_mirrordamage_onlyweapons || DEATH_WEAPONOF(deathtype) != WEP_Null)
864 if(mirrordamage > 0 || mirrorforce > 0)
866 attacker = attacker_save;
868 force = normalize(attacker.origin + attacker.view_ofs - hitloc) * mirrorforce;
869 Damage(attacker, inflictor, attacker, mirrordamage, DEATH_MIRRORDAMAGE.m_id, weaponentity, attacker.origin, force);
873 // Returns total damage applies to creatures
874 float RadiusDamageForSource (entity inflictor, vector inflictororigin, vector inflictorvelocity, entity attacker, float coredamage, float edgedamage, float rad, entity cantbe, entity mustbe,
875 float inflictorselfdamage, float forceintensity, float forcezscale, int deathtype, .entity weaponentity, entity directhitentity)
879 float total_damage_to_creatures;
884 float stat_damagedone;
886 if(RadiusDamage_running)
888 backtrace("RadiusDamage called recursively! Expect stuff to go HORRIBLY wrong.");
892 if (rad < 0) rad = 0;
894 RadiusDamage_running = 1;
896 tfloordmg = autocvar_g_throughfloor_damage;
897 tfloorforce = autocvar_g_throughfloor_force;
899 total_damage_to_creatures = 0;
901 if(deathtype != (WEP_HOOK.m_id | HITTYPE_SECONDARY | HITTYPE_BOUNCE)) // only send gravity bomb damage once
902 if(!(deathtype & HITTYPE_SOUND)) // do not send radial sound damage (bandwidth hog)
904 force = inflictorvelocity;
908 force = normalize(force);
909 if(forceintensity >= 0)
910 Damage_DamageInfo(inflictororigin, coredamage, edgedamage, rad, forceintensity * force, deathtype, 0, attacker);
912 Damage_DamageInfo(inflictororigin, coredamage, edgedamage, -rad, (-forceintensity) * force, deathtype, 0, attacker);
917 targ = WarpZone_FindRadius (inflictororigin, rad + MAX_DAMAGEEXTRARADIUS, false);
921 if ((targ != inflictor) || inflictorselfdamage)
922 if (((cantbe != targ) && !mustbe) || (mustbe == targ))
925 // measure distance from nearest point on target (not origin)
926 // to nearest point on inflictor (not origin)
927 vector nearest = targ.WarpZone_findradius_nearest;
928 vector inflictornearest = NearestPointOnBoundingBox(
929 inflictororigin - (inflictor.maxs - inflictor.mins) * 0.5,
930 inflictororigin + (inflictor.maxs - inflictor.mins) * 0.5,
932 vector diff = inflictornearest - nearest;
934 // round up a little on the damage to ensure full damage on impacts
935 // and turn the distance into a fraction of the radius
936 float dist = max(0, vlen(diff) - bound(MIN_DAMAGEEXTRARADIUS, targ.damageextraradius, MAX_DAMAGEEXTRARADIUS));
941 power -= (dist / rad);
942 // at this point power can't be < 0 or > 1
943 float finaldmg = coredamage * power + edgedamage * (1 - power);
949 vector myblastorigin;
952 myblastorigin = WarpZone_TransformOrigin(targ, inflictororigin);
954 // if it's a player, use the view origin as reference
955 center = CENTER_OR_VIEWOFS(targ);
957 force = normalize(center - myblastorigin);
958 force = force * (finaldmg / coredamage) * forceintensity;
961 // apply special scaling along the z axis if set
962 // NOTE: 0 value is not allowed for compatibility, in the case of weapon cvars not being set
964 force.z *= forcezscale;
966 if(targ != directhitentity)
971 float mininv_f, mininv_d;
973 // test line of sight to multiple positions on box,
974 // and do damage if any of them hit
977 // we know: max stddev of hitratio = 1 / (2 * sqrt(n))
978 // so for a given max stddev:
979 // n = (1 / (2 * max stddev of hitratio))^2
981 mininv_d = (finaldmg * (1-tfloordmg)) / autocvar_g_throughfloor_damage_max_stddev;
982 mininv_f = (vlen(force) * (1-tfloorforce)) / autocvar_g_throughfloor_force_max_stddev;
984 if(autocvar_g_throughfloor_debug)
985 LOG_INFOF("THROUGHFLOOR: D=%f F=%f max(dD)=1/%f max(dF)=1/%f", finaldmg, vlen(force), mininv_d, mininv_f);
988 total = 0.25 * (max(mininv_f, mininv_d) ** 2);
990 if(autocvar_g_throughfloor_debug)
991 LOG_INFOF(" steps=%f", total);
995 total = ceil(bound(autocvar_g_throughfloor_min_steps_player, total, autocvar_g_throughfloor_max_steps_player));
997 total = ceil(bound(autocvar_g_throughfloor_min_steps_other, total, autocvar_g_throughfloor_max_steps_other));
999 if(autocvar_g_throughfloor_debug)
1000 LOG_INFOF(" steps=%f dD=%f dF=%f", total, finaldmg * (1-tfloordmg) / (2 * sqrt(total)), vlen(force) * (1-tfloorforce) / (2 * sqrt(total)));
1002 for(c = 0; c < total; ++c)
1004 //traceline(targ.WarpZone_findradius_findorigin, nearest, MOVE_NOMONSTERS, inflictor);
1005 WarpZone_TraceLine(inflictororigin, WarpZone_UnTransformOrigin(targ, nearest), MOVE_NOMONSTERS, inflictor);
1006 if (trace_fraction == 1 || trace_ent == targ)
1010 hitloc = hitloc + nearest;
1014 nearest.x = targ.origin.x + targ.mins.x + random() * targ.size.x;
1015 nearest.y = targ.origin.y + targ.mins.y + random() * targ.size.y;
1016 nearest.z = targ.origin.z + targ.mins.z + random() * targ.size.z;
1019 nearest = hitloc * (1 / max(1, hits));
1020 hitratio = (hits / total);
1021 a = bound(0, tfloordmg + (1-tfloordmg) * hitratio, 1);
1022 finaldmg = finaldmg * a;
1023 a = bound(0, tfloorforce + (1-tfloorforce) * hitratio, 1);
1026 if(autocvar_g_throughfloor_debug)
1027 LOG_INFOF(" D=%f F=%f", finaldmg, vlen(force));
1030 //if (targ == attacker)
1032 // print("hits ", ftos(hits), " / ", ftos(total));
1033 // print(" finaldmg ", ftos(finaldmg), " force ", vtos(force));
1034 // print(" (", ftos(a), ")\n");
1036 if(finaldmg || force)
1040 total_damage_to_creatures += finaldmg;
1042 if(accuracy_isgooddamage(attacker, targ))
1043 stat_damagedone += finaldmg;
1046 if(targ == directhitentity || DEATH_ISSPECIAL(deathtype))
1047 Damage(targ, inflictor, attacker, finaldmg, deathtype, weaponentity, nearest, force);
1049 Damage(targ, inflictor, attacker, finaldmg, deathtype | HITTYPE_SPLASH, weaponentity, nearest, force);
1057 RadiusDamage_running = 0;
1059 if(!DEATH_ISSPECIAL(deathtype))
1060 accuracy_add(attacker, DEATH_WEAPONOF(deathtype), 0, min(coredamage, stat_damagedone));
1062 return total_damage_to_creatures;
1065 float RadiusDamage(entity inflictor, entity attacker, float coredamage, float edgedamage, float rad, entity cantbe, entity mustbe, float forceintensity, int deathtype, .entity weaponentity, entity directhitentity)
1067 return RadiusDamageForSource(inflictor, (inflictor.origin + (inflictor.mins + inflictor.maxs) * 0.5), inflictor.velocity, attacker, coredamage, edgedamage, rad,
1068 cantbe, mustbe, false, forceintensity, 1, deathtype, weaponentity, directhitentity);
1071 bool Heal(entity targ, entity inflictor, float amount, float limit)
1073 if(game_stopped || (IS_CLIENT(targ) && CS(targ).killcount == FRAGS_SPECTATOR) || STAT(FROZEN, targ) || IS_DEAD(targ))
1076 bool healed = false;
1078 healed = targ.event_heal(targ, inflictor, amount, limit);
1079 // TODO: additional handling? what if the healing kills them? should this abort if healing would do so etc
1080 // TODO: healing fx!
1081 // TODO: armor healing?
1085 float Fire_AddDamage(entity e, entity o, float d, float t, float dt)
1088 float maxtime, mintime, maxdamage, mindamage, maxdps, mindps, totaldamage, totaltime;
1098 if(StatusEffects_active(STATUSEFFECT_Burning, e))
1100 float fireendtime = StatusEffects_gettime(STATUSEFFECT_Burning, e);
1102 mintime = fireendtime - time;
1103 maxtime = max(mintime, t);
1105 mindps = e.fire_damagepersec;
1106 maxdps = max(mindps, dps);
1108 if(maxtime > mintime || maxdps > mindps)
1112 // damage we have right now
1113 mindamage = mindps * mintime;
1115 // damage we want to get
1116 maxdamage = mindamage + d;
1118 // but we can't exceed maxtime * maxdps!
1119 totaldamage = min(maxdamage, maxtime * maxdps);
1123 // totaldamage = min(mindamage + d, maxtime * maxdps)
1125 // totaldamage <= maxtime * maxdps
1126 // ==> totaldamage / maxdps <= maxtime.
1128 // totaldamage / mindps = min(mindamage / mindps + d, maxtime * maxdps / mindps)
1129 // >= min(mintime, maxtime)
1130 // ==> totaldamage / maxdps >= mintime.
1133 // how long do we damage then?
1134 // at least as long as before
1135 // but, never exceed maxdps
1136 totaltime = max(mintime, totaldamage / maxdps); // always <= maxtime due to lemma
1140 // at most as long as maximum allowed
1141 // but, never below mindps
1142 totaltime = min(maxtime, totaldamage / mindps); // always >= mintime due to lemma
1144 // assuming t > mintime, dps > mindps:
1145 // we get d = t * dps = maxtime * maxdps
1146 // totaldamage = min(maxdamage, maxtime * maxdps) = min(... + d, maxtime * maxdps) = maxtime * maxdps
1147 // totaldamage / maxdps = maxtime
1148 // totaldamage / mindps > totaldamage / maxdps = maxtime
1150 // a) totaltime = max(mintime, maxtime) = maxtime
1151 // b) totaltime = min(maxtime, totaldamage / maxdps) = maxtime
1153 // assuming t <= mintime:
1154 // we get maxtime = mintime
1155 // a) totaltime = max(mintime, ...) >= mintime, also totaltime <= maxtime by the lemma, therefore totaltime = mintime = maxtime
1156 // b) totaltime = min(maxtime, ...) <= maxtime, also totaltime >= mintime by the lemma, therefore totaltime = mintime = maxtime
1158 // assuming dps <= mindps:
1159 // we get mindps = maxdps.
1160 // With this, the lemma says that mintime <= totaldamage / mindps = totaldamage / maxdps <= maxtime.
1161 // a) totaltime = max(mintime, totaldamage / maxdps) = totaldamage / maxdps
1162 // b) totaltime = min(maxtime, totaldamage / mindps) = totaldamage / maxdps
1164 e.fire_damagepersec = totaldamage / totaltime;
1165 StatusEffects_apply(STATUSEFFECT_Burning, e, time + totaltime, 0);
1166 if(totaldamage > 1.2 * mindamage)
1168 e.fire_deathtype = dt;
1169 if(e.fire_owner != o)
1172 e.fire_hitsound = false;
1175 if(accuracy_isgooddamage(o, e))
1176 accuracy_add(o, DEATH_WEAPONOF(dt), 0, max(0, totaldamage - mindamage));
1177 return max(0, totaldamage - mindamage); // can never be negative, but to make sure
1184 e.fire_damagepersec = dps;
1185 StatusEffects_apply(STATUSEFFECT_Burning, e, time + t, 0);
1186 e.fire_deathtype = dt;
1188 e.fire_hitsound = false;
1189 if(accuracy_isgooddamage(o, e))
1190 accuracy_add(o, DEATH_WEAPONOF(dt), 0, d);
1195 void Fire_ApplyDamage(entity e)
1200 for(t = 0, o = e.owner; o.owner && t < 16; o = o.owner, ++t);
1201 if(IS_NOT_A_CLIENT(o))
1204 float fireendtime = StatusEffects_gettime(STATUSEFFECT_Burning, e);
1205 t = min(frametime, fireendtime - time);
1206 d = e.fire_damagepersec * t;
1208 hi = e.fire_owner.hitsound_damage_dealt;
1209 ty = e.fire_owner.typehitsound;
1210 Damage(e, e, e.fire_owner, d, e.fire_deathtype, DMG_NOWEP, e.origin, '0 0 0');
1211 if(e.fire_hitsound && e.fire_owner)
1213 e.fire_owner.hitsound_damage_dealt = hi;
1214 e.fire_owner.typehitsound = ty;
1216 e.fire_hitsound = true;
1218 if(!IS_INDEPENDENT_PLAYER(e) && !STAT(FROZEN, e))
1220 IL_EACH(g_damagedbycontents, it.damagedbycontents && it != e,
1222 if(!IS_DEAD(it) && it.takedamage && !IS_INDEPENDENT_PLAYER(it))
1223 if(boxesoverlap(e.absmin, e.absmax, it.absmin, it.absmax))
1225 t = autocvar_g_balance_firetransfer_time * (fireendtime - time);
1226 d = autocvar_g_balance_firetransfer_damage * e.fire_damagepersec * t;
1227 Fire_AddDamage(it, o, d, t, DEATH_FIRE.m_id);