3 #include <common/effects/all.qh>
6 #include <server/gamelog.qh>
7 #include <server/mutators/_mod.qh>
10 #include "spawnpoints.qh"
11 #include "../common/state.qh"
12 #include "../common/physics/player.qh"
13 #include "../common/t_items.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/notifications/all.qh"
26 #include "../common/physics/movetypes/movetypes.qh"
27 #include "../common/playerstats.qh"
28 #include "../common/teams.qh"
29 #include "../common/util.qh"
30 #include <common/gamemodes/_mod.qh>
31 #include <common/gamemodes/rules.qh>
32 #include <common/weapons/_all.qh>
33 #include "../lib/csqcmodel/sv_model.qh"
34 #include "../lib/warpzone/common.qh"
36 void UpdateFrags(entity player, int f)
38 GameRules_scoring_add_team(player, SCORE, f);
41 void GiveFrags(entity attacker, entity targ, float f, int deathtype, .entity weaponentity)
43 // TODO route through PlayerScores instead
44 if(game_stopped) return;
51 GameRules_scoring_add(attacker, SUICIDES, 1);
56 GameRules_scoring_add(attacker, TEAMKILLS, 1);
62 GameRules_scoring_add(attacker, KILLS, 1);
63 if(!warmup_stage && targ.playerid)
64 PlayerStats_GameReport_Event_Player(attacker, sprintf("kills-%d", targ.playerid), 1);
67 GameRules_scoring_add(targ, DEATHS, 1);
69 // FIXME fix the mess this is (we have REAL points now!)
70 if(MUTATOR_CALLHOOK(GiveFragsForKill, attacker, targ, f, deathtype, attacker.(weaponentity)))
73 attacker.totalfrags += f;
76 UpdateFrags(attacker, f);
79 string AppendItemcodes(string s, entity player)
81 for(int slot = 0; slot < MAX_WEAPONSLOTS; ++slot)
83 .entity weaponentity = weaponentities[slot];
84 int w = player.(weaponentity).m_weapon.m_id;
86 w = player.(weaponentity).cnt; // previous weapon
87 if(w != 0 || slot == 0)
88 s = strcat(s, ftos(w));
90 if(time < STAT(STRENGTH_FINISHED, player))
92 if(time < STAT(INVINCIBLE_FINISHED, player))
94 if(PHYS_INPUT_BUTTON_CHAT(player))
96 // TODO: include these codes as a flag on the item itself
97 MUTATOR_CALLHOOK(LogDeath_AppendItemCodes, player, s);
98 s = M_ARGV(1, string);
102 void LogDeath(string mode, int deathtype, entity killer, entity killed)
105 if(!autocvar_sv_eventlog)
107 s = strcat(":kill:", mode);
108 s = strcat(s, ":", ftos(killer.playerid));
109 s = strcat(s, ":", ftos(killed.playerid));
110 s = strcat(s, ":type=", Deathtype_Name(deathtype));
111 s = strcat(s, ":items=");
112 s = AppendItemcodes(s, killer);
115 s = strcat(s, ":victimitems=");
116 s = AppendItemcodes(s, killed);
121 void Obituary_SpecialDeath(
125 string s1, string s2, string s3,
126 float f1, float f2, float f3)
128 if(!DEATH_ISSPECIAL(deathtype))
130 backtrace("Obituary_SpecialDeath called without a special deathtype?\n");
134 entity deathent = REGISTRY_GET(Deathtypes, deathtype - DT_FIRST);
137 backtrace("Obituary_SpecialDeath: Could not find deathtype entity!\n");
141 if(g_cts && deathtype == DEATH_KILL.m_id)
142 return; // TODO: somehow put this in CTS gamemode file!
144 Notification death_message = (murder) ? deathent.death_msgmurder : deathent.death_msgself;
147 Send_Notification_WOCOVA(
155 Send_Notification_WOCOVA(
159 death_message.nent_msginfo,
166 float Obituary_WeaponDeath(
170 string s1, string s2, string s3,
173 Weapon death_weapon = DEATH_WEAPONOF(deathtype);
174 if (death_weapon == WEP_Null)
177 w_deathtype = deathtype;
178 Notification death_message = ((murder) ? death_weapon.wr_killmessage(death_weapon) : death_weapon.wr_suicidemessage(death_weapon));
183 Send_Notification_WOCOVA(
191 // send the info part to everyone
192 Send_Notification_WOCOVA(
196 death_message.nent_msginfo,
204 "Obituary_WeaponDeath(): ^1Deathtype ^7(%d)^1 has no notification for weapon %s!\n",
213 bool frag_centermessage_override(entity attacker, entity targ, int deathtype, int kill_count_to_attacker, int kill_count_to_target, string attacker_name)
215 if(deathtype == DEATH_FIRE.m_id)
217 Send_Notification(NOTIF_ONE, attacker, MSG_CHOICE, CHOICE_FRAG_FIRE, targ.netname, kill_count_to_attacker, (IS_BOT_CLIENT(targ) ? -1 : CS(targ).ping));
218 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));
222 return MUTATOR_CALLHOOK(FragCenterMessage, attacker, targ, deathtype, kill_count_to_attacker, kill_count_to_target);
225 void Obituary(entity attacker, entity inflictor, entity targ, int deathtype, .entity weaponentity)
228 if (!IS_PLAYER(targ)) { backtrace("Obituary called on non-player?!\n"); return; }
231 float notif_firstblood = false;
232 float kill_count_to_attacker, kill_count_to_target;
233 bool notif_anonymous = false;
234 string attacker_name = attacker.netname;
236 // Set final information for the death
237 targ.death_origin = targ.origin;
238 string deathlocation = (autocvar_notification_server_allows_location ? NearestLocation(targ.death_origin) : "");
240 // Abort now if a mutator requests it
241 if (MUTATOR_CALLHOOK(ClientObituary, inflictor, attacker, targ, deathtype, attacker.(weaponentity))) { CS(targ).killcount = 0; return; }
242 notif_anonymous = M_ARGV(5, bool);
245 attacker_name = "Anonymous player";
247 #ifdef NOTIFICATIONS_DEBUG
250 "Obituary(%s, %s, %s, %s = %d);\n",
254 Deathtype_Name(deathtype),
265 if(DEATH_ISSPECIAL(deathtype))
267 if(deathtype == DEATH_TEAMCHANGE.m_id || deathtype == DEATH_AUTOTEAMCHANGE.m_id)
269 Obituary_SpecialDeath(targ, false, deathtype, targ.netname, deathlocation, "", targ.team, 0, 0);
273 switch(DEATH_ENT(deathtype))
275 case DEATH_MIRRORDAMAGE:
277 Obituary_SpecialDeath(targ, false, deathtype, targ.netname, deathlocation, "", CS(targ).killcount, 0, 0);
283 Obituary_SpecialDeath(targ, false, deathtype, targ.netname, deathlocation, "", CS(targ).killcount, 0, 0);
289 else if (!Obituary_WeaponDeath(targ, false, deathtype, targ.netname, deathlocation, "", CS(targ).killcount, 0))
291 backtrace("SUICIDE: what the hell happened here?\n");
294 LogDeath("suicide", deathtype, targ, targ);
295 if(deathtype != DEATH_AUTOTEAMCHANGE.m_id) // special case: don't negate frags if auto switched
296 GiveFrags(attacker, targ, -1, deathtype, weaponentity);
302 else if(IS_PLAYER(attacker))
304 if(SAME_TEAM(attacker, targ))
306 LogDeath("tk", deathtype, attacker, targ);
307 GiveFrags(attacker, targ, -1, deathtype, weaponentity);
309 CS(attacker).killcount = 0;
311 Send_Notification(NOTIF_ONE, attacker, MSG_CENTER, CENTER_DEATH_TEAMKILL_FRAG, targ.netname);
312 Send_Notification(NOTIF_ONE, targ, MSG_CENTER, CENTER_DEATH_TEAMKILL_FRAGGED, attacker_name);
313 Send_Notification(NOTIF_ALL, NULL, MSG_INFO, APP_TEAM_NUM(targ.team, INFO_DEATH_TEAMKILL), targ.netname, attacker_name, deathlocation, CS(targ).killcount);
315 // In this case, the death message will ALWAYS be "foo was betrayed by bar"
316 // No need for specific death/weapon messages...
320 LogDeath("frag", deathtype, attacker, targ);
321 GiveFrags(attacker, targ, 1, deathtype, weaponentity);
323 CS(attacker).taunt_soundtime = time + 1;
324 CS(attacker).killcount = CS(attacker).killcount + 1;
326 attacker.killsound += 1;
328 // TODO: improve SPREE_ITEM and KILL_SPREE_LIST
329 // these 2 macros are spread over multiple files
330 #define SPREE_ITEM(counta,countb,center,normal,gentle) \
332 Send_Notification(NOTIF_ONE, attacker, MSG_ANNCE, ANNCE_KILLSTREAK_##countb); \
334 PlayerStats_GameReport_Event_Player(attacker, PLAYERSTATS_ACHIEVEMENT_KILL_SPREE_##counta, 1); \
337 switch(CS(attacker).killcount)
344 if(!warmup_stage && !checkrules_firstblood)
346 checkrules_firstblood = true;
347 notif_firstblood = true; // modify the current messages so that they too show firstblood information
348 PlayerStats_GameReport_Event_Player(attacker, PLAYERSTATS_ACHIEVEMENT_FIRSTBLOOD, 1);
349 PlayerStats_GameReport_Event_Player(targ, PLAYERSTATS_ACHIEVEMENT_FIRSTVICTIM, 1);
351 // tell spree_inf and spree_cen that this is a first-blood and first-victim event
352 kill_count_to_attacker = -1;
353 kill_count_to_target = -2;
357 kill_count_to_attacker = CS(attacker).killcount;
358 kill_count_to_target = 0;
369 kill_count_to_attacker,
370 (IS_BOT_CLIENT(targ) ? -1 : CS(targ).ping)
378 kill_count_to_target,
379 GetResource(attacker, RES_HEALTH),
380 GetResource(attacker, RES_ARMOR),
381 (IS_BOT_CLIENT(attacker) ? -1 : CS(attacker).ping)
384 else if(!frag_centermessage_override(attacker, targ, deathtype, kill_count_to_attacker, kill_count_to_target, attacker_name))
392 kill_count_to_attacker,
393 (IS_BOT_CLIENT(targ) ? -1 : CS(targ).ping)
401 kill_count_to_target,
402 GetResource(attacker, RES_HEALTH),
403 GetResource(attacker, RES_ARMOR),
404 (IS_BOT_CLIENT(attacker) ? -1 : CS(attacker).ping)
409 if(deathtype == DEATH_BUFF.m_id)
410 f3 = buff_FirstFromFlags(STAT(BUFFS, attacker)).m_id;
412 if (!Obituary_WeaponDeath(targ, true, deathtype, targ.netname, attacker_name, deathlocation, CS(targ).killcount, kill_count_to_attacker))
413 Obituary_SpecialDeath(targ, true, deathtype, targ.netname, attacker_name, deathlocation, CS(targ).killcount, kill_count_to_attacker, f3);
422 switch(DEATH_ENT(deathtype))
424 // For now, we're just forcing HURTTRIGGER to behave as "DEATH_VOID" and giving it no special options...
425 // Later on you will only be able to make custom messages using DEATH_CUSTOM,
426 // and there will be a REAL DEATH_VOID implementation which mappers will use.
427 case DEATH_HURTTRIGGER:
429 Obituary_SpecialDeath(targ, false, deathtype,
441 Obituary_SpecialDeath(targ, false, deathtype,
443 ((strstrofs(deathmessage, "%", 0) < 0) ? strcat("%s ", deathmessage) : deathmessage),
453 Obituary_SpecialDeath(targ, false, deathtype, targ.netname, deathlocation, "", CS(targ).killcount, 0, 0);
458 LogDeath("accident", deathtype, targ, targ);
459 GiveFrags(targ, targ, -1, deathtype, weaponentity);
461 if(GameRules_scoring_add(targ, SCORE, 0) == -5)
463 Send_Notification(NOTIF_ONE, targ, MSG_ANNCE, ANNCE_ACHIEVEMENT_BOTLIKE);
466 PlayerStats_GameReport_Event_Player(attacker, PLAYERSTATS_ACHIEVEMENT_BOTLIKE, 1);
471 // reset target kill count
472 CS(targ).killcount = 0;
475 void Ice_Think(entity this)
477 if(!STAT(FROZEN, this.owner) || this.owner.iceblock != this)
482 vector ice_org = this.owner.origin - '0 0 16';
483 if (this.origin != ice_org)
484 setorigin(this, ice_org);
485 this.nextthink = time;
488 void Freeze(entity targ, float revivespeed, int frozen_type, bool show_waypoint)
490 if(!IS_PLAYER(targ) && !IS_MONSTER(targ)) // TODO: only specified entities can be freezed
493 if(STAT(FROZEN, targ))
496 float targ_maxhealth = ((IS_MONSTER(targ)) ? targ.max_health : start_health);
498 STAT(FROZEN, targ) = frozen_type;
499 STAT(REVIVE_PROGRESS, targ) = ((frozen_type == FROZEN_TEMP_DYING) ? 1 : 0);
500 SetResource(targ, RES_HEALTH, ((frozen_type == FROZEN_TEMP_DYING) ? targ_maxhealth : 1));
501 targ.revive_speed = revivespeed;
503 IL_REMOVE(g_bot_targets, targ);
504 targ.bot_attack = false;
505 targ.freeze_time = time;
507 entity ice = new(ice);
509 ice.scale = targ.scale;
510 // set_movetype(ice, MOVETYPE_FOLLOW) would rotate the ice model with the player
511 setthink(ice, Ice_Think);
512 ice.nextthink = time;
513 ice.frame = floor(random() * 21); // ice model has 20 different looking frames
514 setmodel(ice, MDL_ICE);
516 ice.colormod = Team_ColorRGB(targ.team);
517 ice.glowmod = ice.colormod;
519 targ.revival_time = 0;
523 RemoveGrapplingHooks(targ);
525 FOREACH_CLIENT(IS_PLAYER(it),
527 for(int slot = 0; slot < MAX_WEAPONSLOTS; ++slot)
529 .entity weaponentity = weaponentities[slot];
530 if(it.(weaponentity).hook.aiment == targ)
531 RemoveHook(it.(weaponentity).hook);
536 if(MUTATOR_CALLHOOK(Freeze, targ, revivespeed, frozen_type) || show_waypoint)
537 WaypointSprite_Spawn(WP_Frozen, 0, 0, targ, '0 0 64', NULL, targ.team, targ, waypointsprite_attached, true, RADARICON_WAYPOINT);
540 void Unfreeze(entity targ, bool reset_health)
542 if(!STAT(FROZEN, targ))
545 if (reset_health && STAT(FROZEN, targ) != FROZEN_TEMP_DYING)
546 SetResource(targ, RES_HEALTH, ((IS_PLAYER(targ)) ? start_health : targ.max_health));
548 targ.pauseregen_finished = time + autocvar_g_balance_pause_health_regen;
550 STAT(FROZEN, targ) = 0;
551 STAT(REVIVE_PROGRESS, targ) = 0;
552 targ.revival_time = time;
554 IL_PUSH(g_bot_targets, targ);
555 targ.bot_attack = true;
557 WaypointSprite_Kill(targ.waypointsprite_attached);
559 FOREACH_CLIENT(IS_PLAYER(it),
561 for(int slot = 0; slot < MAX_WEAPONSLOTS; ++slot)
563 .entity weaponentity = weaponentities[slot];
564 if(it.(weaponentity).hook.aiment == targ)
565 RemoveHook(it.(weaponentity).hook);
569 // remove the ice block
571 delete(targ.iceblock);
572 targ.iceblock = NULL;
574 MUTATOR_CALLHOOK(Unfreeze, targ);
577 void Damage(entity targ, entity inflictor, entity attacker, float damage, int deathtype, .entity weaponentity, vector hitloc, vector force)
579 float complainteamdamage = 0;
580 float mirrordamage = 0;
581 float mirrorforce = 0;
583 if (game_stopped || (IS_CLIENT(targ) && CS(targ).killcount == FRAGS_SPECTATOR))
586 entity attacker_save = attacker;
588 // special rule: gravity bomb does not hit team mates (other than for disconnecting the hook)
589 if(DEATH_ISWEAPON(deathtype, WEP_HOOK) || DEATH_ISWEAPON(deathtype, WEP_TUBA))
591 if(IS_PLAYER(targ) && SAME_TEAM(targ, attacker))
597 if(deathtype == DEATH_KILL.m_id || deathtype == DEATH_TEAMCHANGE.m_id || deathtype == DEATH_AUTOTEAMCHANGE.m_id)
599 // exit the vehicle before killing (fixes a crash)
600 if(IS_PLAYER(targ) && targ.vehicle)
601 vehicles_exit(targ.vehicle, VHEF_RELEASE);
603 // These are ALWAYS lethal
604 // No damage modification here
605 // Instead, prepare the victim for his death...
606 SetResourceExplicit(targ, RES_ARMOR, 0);
607 targ.spawnshieldtime = 0;
608 SetResourceExplicit(targ, RES_HEALTH, 0.9); // this is < 1
609 targ.flags -= targ.flags & FL_GODMODE;
612 else if(deathtype == DEATH_MIRRORDAMAGE.m_id || deathtype == DEATH_NOAMMO.m_id)
618 // nullify damage if teamplay is on
619 if(deathtype != DEATH_TELEFRAG.m_id)
620 if(IS_PLAYER(attacker))
622 if(IS_PLAYER(targ) && targ != attacker && (IS_INDEPENDENT_PLAYER(attacker) || IS_INDEPENDENT_PLAYER(targ)))
627 else if(SAME_TEAM(attacker, targ))
629 if(autocvar_teamplay_mode == 1)
631 else if(attacker != targ)
633 if(autocvar_teamplay_mode == 2)
635 if(IS_PLAYER(targ) && !IS_DEAD(targ))
637 attacker.dmg_team = attacker.dmg_team + damage;
638 complainteamdamage = attacker.dmg_team - autocvar_g_teamdamage_threshold;
641 else if(autocvar_teamplay_mode == 3)
643 else if(autocvar_teamplay_mode == 4)
645 if(IS_PLAYER(targ) && !IS_DEAD(targ))
647 attacker.dmg_team = attacker.dmg_team + damage;
648 complainteamdamage = attacker.dmg_team - autocvar_g_teamdamage_threshold;
649 if(complainteamdamage > 0)
650 mirrordamage = autocvar_g_mirrordamage * complainteamdamage;
651 mirrorforce = autocvar_g_mirrordamage * vlen(force);
652 damage = autocvar_g_friendlyfire * damage;
653 // mirrordamage will be used LATER
655 if(autocvar_g_mirrordamage_virtual)
657 vector v = healtharmor_applydamage(GetResource(attacker, RES_ARMOR), autocvar_g_balance_armor_blockpercent, deathtype, mirrordamage);
658 attacker.dmg_take += v.x;
659 attacker.dmg_save += v.y;
660 attacker.dmg_inflictor = inflictor;
665 if(autocvar_g_friendlyfire_virtual)
667 vector v = healtharmor_applydamage(GetResource(targ, RES_ARMOR), autocvar_g_balance_armor_blockpercent, deathtype, damage);
668 targ.dmg_take += v.x;
669 targ.dmg_save += v.y;
670 targ.dmg_inflictor = inflictor;
672 if(!autocvar_g_friendlyfire_virtual_force)
676 else if(!targ.canteamdamage)
683 if (!DEATH_ISSPECIAL(deathtype))
685 damage *= g_weapondamagefactor;
686 mirrordamage *= g_weapondamagefactor;
687 complainteamdamage *= g_weapondamagefactor;
688 force = force * g_weaponforcefactor;
689 mirrorforce *= g_weaponforcefactor;
692 // should this be changed at all? If so, in what way?
693 MUTATOR_CALLHOOK(Damage_Calculate, inflictor, attacker, targ, deathtype, damage, mirrordamage, force, attacker.(weaponentity));
694 damage = M_ARGV(4, float);
695 mirrordamage = M_ARGV(5, float);
696 force = M_ARGV(6, vector);
698 if(IS_PLAYER(targ) && damage > 0 && attacker)
700 for(int slot = 0; slot < MAX_WEAPONSLOTS; ++slot)
702 .entity went = weaponentities[slot];
703 if(targ.(went).hook && targ.(went).hook.aiment == attacker)
704 RemoveHook(targ.(went).hook);
708 if(STAT(FROZEN, targ) && !ITEM_DAMAGE_NEEDKILL(deathtype)
709 && deathtype != DEATH_TEAMCHANGE.m_id && deathtype != DEATH_AUTOTEAMCHANGE.m_id)
711 if(autocvar_g_frozen_revive_falldamage > 0 && deathtype == DEATH_FALL.m_id && damage >= autocvar_g_frozen_revive_falldamage)
713 Unfreeze(targ, false);
714 SetResource(targ, RES_HEALTH, autocvar_g_frozen_revive_falldamage_health);
715 Send_Effect(EFFECT_ICEORGLASS, targ.origin, '0 0 0', 3);
716 Send_Notification(NOTIF_ALL, NULL, MSG_INFO, INFO_FREEZETAG_REVIVED_FALL, targ.netname);
717 Send_Notification(NOTIF_ONE, targ, MSG_CENTER, CENTER_FREEZETAG_REVIVE_SELF);
721 force *= autocvar_g_frozen_force;
724 if(IS_PLAYER(targ) && STAT(FROZEN, targ)
725 && ITEM_DAMAGE_NEEDKILL(deathtype) && !autocvar_g_frozen_damage_trigger)
727 Send_Effect(EFFECT_TELEPORT, targ.origin, '0 0 0', 1);
729 entity spot = SelectSpawnPoint(targ, false);
733 targ.deadflag = DEAD_NO;
735 targ.angles = spot.angles;
738 targ.effects |= EF_TELEPORT_BIT;
740 targ.angles_z = 0; // never spawn tilted even if the spot says to
741 targ.fixangle = true; // turn this way immediately
742 targ.velocity = '0 0 0';
743 targ.avelocity = '0 0 0';
744 targ.punchangle = '0 0 0';
745 targ.punchvector = '0 0 0';
746 targ.oldvelocity = targ.velocity;
748 targ.spawnorigin = spot.origin;
749 setorigin(targ, spot.origin + '0 0 1' * (1 - targ.mins.z - 24));
750 // don't reset back to last position, even if new position is stuck in solid
751 targ.oldorigin = targ.origin;
753 Send_Effect(EFFECT_TELEPORT, targ.origin, '0 0 0', 1);
757 if(!MUTATOR_IS_ENABLED(mutator_instagib))
759 // apply strength multiplier
760 if (attacker.items & ITEM_Strength.m_itemid)
764 damage = damage * autocvar_g_balance_powerup_strength_selfdamage;
765 force = force * autocvar_g_balance_powerup_strength_selfforce;
769 damage = damage * autocvar_g_balance_powerup_strength_damage;
770 force = force * autocvar_g_balance_powerup_strength_force;
774 // apply invincibility multiplier
775 if (targ.items & ITEM_Shield.m_itemid)
777 damage = damage * autocvar_g_balance_powerup_invincible_takedamage;
778 if (targ != attacker)
780 force = force * autocvar_g_balance_powerup_invincible_takeforce;
785 if (targ == attacker)
786 damage = damage * autocvar_g_balance_selfdamagepercent; // Partial damage if the attacker hits himself
791 if(deathtype != DEATH_BUFF.m_id)
792 if(targ.takedamage == DAMAGE_AIM)
796 if(IS_VEHICLE(targ) && targ.owner)
801 if(IS_PLAYER(victim) || (IS_TURRET(victim) && victim.active == ACTIVE_ACTIVE) || IS_MONSTER(victim) || MUTATOR_CALLHOOK(PlayHitsound, victim, attacker))
803 if(DIFF_TEAM(victim, attacker) && !STAT(FROZEN, victim))
807 if(deathtype != DEATH_FIRE.m_id)
809 if(PHYS_INPUT_BUTTON_CHAT(victim))
810 attacker.typehitsound += 1;
812 attacker.damage_dealt += damage;
815 damage_goodhits += 1;
816 damage_gooddamage += damage;
818 if (!DEATH_ISSPECIAL(deathtype))
820 if(IS_PLAYER(targ)) // don't do this for vehicles
826 else if(IS_PLAYER(attacker))
828 // if enemy gets frozen in this frame and receives other damage don't
829 // play the typehitsound e.g. when hit by multiple bullets of the shotgun
830 if (deathtype != DEATH_FIRE.m_id && (!STAT(FROZEN, victim) || time > victim.freeze_time))
832 attacker.typehitsound += 1;
834 if(complainteamdamage > 0)
835 if(time > CS(attacker).teamkill_complain)
837 CS(attacker).teamkill_complain = time + 5;
838 CS(attacker).teamkill_soundtime = time + 0.4;
839 CS(attacker).teamkill_soundsource = targ;
847 if (targ.damageforcescale)
849 if (!IS_PLAYER(targ) || time >= targ.spawnshieldtime || targ == attacker)
851 vector farce = damage_explosion_calcpush(targ.damageforcescale * force, targ.velocity, autocvar_g_balance_damagepush_speedfactor);
852 if(targ.move_movetype == MOVETYPE_PHYSICS)
854 entity farcent = new(farce);
855 farcent.enemy = targ;
856 farcent.movedir = farce * 10;
858 farcent.movedir = farcent.movedir * targ.mass;
859 farcent.origin = hitloc;
860 farcent.forcetype = FORCETYPE_FORCEATPOS;
861 farcent.nextthink = time + 0.1;
862 setthink(farcent, SUB_Remove);
866 targ.velocity = targ.velocity + farce;
868 UNSET_ONGROUND(targ);
869 UpdateCSQCProjectile(targ);
872 if (damage != 0 || (targ.damageforcescale && force))
873 if (targ.event_damage)
874 targ.event_damage (targ, inflictor, attacker, damage, deathtype, weaponentity, hitloc, force);
876 // apply mirror damage if any
877 if(!autocvar_g_mirrordamage_onlyweapons || DEATH_WEAPONOF(deathtype) != WEP_Null)
878 if(mirrordamage > 0 || mirrorforce > 0)
880 attacker = attacker_save;
882 force = normalize(attacker.origin + attacker.view_ofs - hitloc) * mirrorforce;
883 Damage(attacker, inflictor, attacker, mirrordamage, DEATH_MIRRORDAMAGE.m_id, weaponentity, attacker.origin, force);
887 float RadiusDamageForSource (entity inflictor, vector inflictororigin, vector inflictorvelocity, entity attacker, float coredamage, float edgedamage, float rad, entity cantbe, entity mustbe,
888 float inflictorselfdamage, float forceintensity, float forcezscale, int deathtype, .entity weaponentity, entity directhitentity)
889 // Returns total damage applies to creatures
893 float total_damage_to_creatures;
898 float stat_damagedone;
900 if(RadiusDamage_running)
902 backtrace("RadiusDamage called recursively! Expect stuff to go HORRIBLY wrong.");
906 RadiusDamage_running = 1;
908 tfloordmg = autocvar_g_throughfloor_damage;
909 tfloorforce = autocvar_g_throughfloor_force;
911 total_damage_to_creatures = 0;
913 if(deathtype != (WEP_HOOK.m_id | HITTYPE_SECONDARY | HITTYPE_BOUNCE)) // only send gravity bomb damage once
914 if(DEATH_WEAPONOF(deathtype) != WEP_TUBA) // do not send tuba damage (bandwidth hog)
916 force = inflictorvelocity;
920 force = normalize(force);
921 if(forceintensity >= 0)
922 Damage_DamageInfo(inflictororigin, coredamage, edgedamage, rad, forceintensity * force, deathtype, 0, attacker);
924 Damage_DamageInfo(inflictororigin, coredamage, edgedamage, -rad, (-forceintensity) * force, deathtype, 0, attacker);
929 targ = WarpZone_FindRadius (inflictororigin, rad + MAX_DAMAGEEXTRARADIUS, false);
933 if ((targ != inflictor) || inflictorselfdamage)
934 if (((cantbe != targ) && !mustbe) || (mustbe == targ))
941 // LordHavoc: measure distance to nearest point on target (not origin)
942 // (this guarentees 100% damage on a touch impact)
943 nearest = targ.WarpZone_findradius_nearest;
944 diff = targ.WarpZone_findradius_dist;
945 // round up a little on the damage to ensure full damage on impacts
946 // and turn the distance into a fraction of the radius
947 power = 1 - ((vlen (diff) - bound(MIN_DAMAGEEXTRARADIUS, targ.damageextraradius, MAX_DAMAGEEXTRARADIUS)) / rad);
949 //bprint(ftos(power));
950 //if (targ == attacker)
951 // print(ftos(power), "\n");
957 finaldmg = coredamage * power + edgedamage * (1 - power);
963 vector myblastorigin;
966 myblastorigin = WarpZone_TransformOrigin(targ, inflictororigin);
968 // if it's a player, use the view origin as reference
969 center = CENTER_OR_VIEWOFS(targ);
971 force = normalize(center - myblastorigin);
972 force = force * (finaldmg / coredamage) * forceintensity;
975 // apply special scaling along the z axis if set
976 // NOTE: 0 value is not allowed for compatibility, in the case of weapon cvars not being set
978 force.z *= forcezscale;
980 if(targ != directhitentity)
985 float mininv_f, mininv_d;
987 // test line of sight to multiple positions on box,
988 // and do damage if any of them hit
991 // we know: max stddev of hitratio = 1 / (2 * sqrt(n))
992 // so for a given max stddev:
993 // n = (1 / (2 * max stddev of hitratio))^2
995 mininv_d = (finaldmg * (1-tfloordmg)) / autocvar_g_throughfloor_damage_max_stddev;
996 mininv_f = (vlen(force) * (1-tfloorforce)) / autocvar_g_throughfloor_force_max_stddev;
998 if(autocvar_g_throughfloor_debug)
999 LOG_INFOF("THROUGHFLOOR: D=%f F=%f max(dD)=1/%f max(dF)=1/%f", finaldmg, vlen(force), mininv_d, mininv_f);
1002 total = 0.25 * (max(mininv_f, mininv_d) ** 2);
1004 if(autocvar_g_throughfloor_debug)
1005 LOG_INFOF(" steps=%f", total);
1008 if (IS_PLAYER(targ))
1009 total = ceil(bound(autocvar_g_throughfloor_min_steps_player, total, autocvar_g_throughfloor_max_steps_player));
1011 total = ceil(bound(autocvar_g_throughfloor_min_steps_other, total, autocvar_g_throughfloor_max_steps_other));
1013 if(autocvar_g_throughfloor_debug)
1014 LOG_INFOF(" steps=%f dD=%f dF=%f", total, finaldmg * (1-tfloordmg) / (2 * sqrt(total)), vlen(force) * (1-tfloorforce) / (2 * sqrt(total)));
1016 for(c = 0; c < total; ++c)
1018 //traceline(targ.WarpZone_findradius_findorigin, nearest, MOVE_NOMONSTERS, inflictor);
1019 WarpZone_TraceLine(inflictororigin, WarpZone_UnTransformOrigin(targ, nearest), MOVE_NOMONSTERS, inflictor);
1020 if (trace_fraction == 1 || trace_ent == targ)
1024 hitloc = hitloc + nearest;
1028 nearest.x = targ.origin.x + targ.mins.x + random() * targ.size.x;
1029 nearest.y = targ.origin.y + targ.mins.y + random() * targ.size.y;
1030 nearest.z = targ.origin.z + targ.mins.z + random() * targ.size.z;
1033 nearest = hitloc * (1 / max(1, hits));
1034 hitratio = (hits / total);
1035 a = bound(0, tfloordmg + (1-tfloordmg) * hitratio, 1);
1036 finaldmg = finaldmg * a;
1037 a = bound(0, tfloorforce + (1-tfloorforce) * hitratio, 1);
1040 if(autocvar_g_throughfloor_debug)
1041 LOG_INFOF(" D=%f F=%f", finaldmg, vlen(force));
1044 //if (targ == attacker)
1046 // print("hits ", ftos(hits), " / ", ftos(total));
1047 // print(" finaldmg ", ftos(finaldmg), " force ", vtos(force));
1048 // print(" (", ftos(a), ")\n");
1050 if(finaldmg || force)
1054 total_damage_to_creatures += finaldmg;
1056 if(accuracy_isgooddamage(attacker, targ))
1057 stat_damagedone += finaldmg;
1060 if(targ == directhitentity || DEATH_ISSPECIAL(deathtype))
1061 Damage(targ, inflictor, attacker, finaldmg, deathtype, weaponentity, nearest, force);
1063 Damage(targ, inflictor, attacker, finaldmg, deathtype | HITTYPE_SPLASH, weaponentity, nearest, force);
1071 RadiusDamage_running = 0;
1073 if(!DEATH_ISSPECIAL(deathtype))
1074 accuracy_add(attacker, DEATH_WEAPONOF(deathtype), 0, min(coredamage, stat_damagedone));
1076 return total_damage_to_creatures;
1079 float RadiusDamage(entity inflictor, entity attacker, float coredamage, float edgedamage, float rad, entity cantbe, entity mustbe, float forceintensity, int deathtype, .entity weaponentity, entity directhitentity)
1081 return RadiusDamageForSource(inflictor, (inflictor.origin + (inflictor.mins + inflictor.maxs) * 0.5), inflictor.velocity, attacker, coredamage, edgedamage, rad,
1082 cantbe, mustbe, false, forceintensity, 1, deathtype, weaponentity, directhitentity);
1085 bool Heal(entity targ, entity inflictor, float amount, float limit)
1087 if(game_stopped || (IS_CLIENT(targ) && CS(targ).killcount == FRAGS_SPECTATOR) || STAT(FROZEN, targ) || IS_DEAD(targ))
1090 bool healed = false;
1092 healed = targ.event_heal(targ, inflictor, amount, limit);
1093 // TODO: additional handling? what if the healing kills them? should this abort if healing would do so etc
1094 // TODO: healing fx!
1095 // TODO: armor healing?
1099 float Fire_IsBurning(entity e)
1101 return (time < e.fire_endtime);
1104 float Fire_AddDamage(entity e, entity o, float d, float t, float dt)
1107 float maxtime, mintime, maxdamage, mindamage, maxdps, mindps, totaldamage, totaltime;
1118 // print("adding a fire burner to ", e.classname, "\n");
1119 e.fire_burner = new(fireburner);
1120 setthink(e.fire_burner, fireburner_think);
1121 e.fire_burner.nextthink = time;
1122 e.fire_burner.owner = e;
1128 if(Fire_IsBurning(e))
1130 mintime = e.fire_endtime - time;
1131 maxtime = max(mintime, t);
1133 mindps = e.fire_damagepersec;
1134 maxdps = max(mindps, dps);
1136 if(maxtime > mintime || maxdps > mindps)
1140 // damage we have right now
1141 mindamage = mindps * mintime;
1143 // damage we want to get
1144 maxdamage = mindamage + d;
1146 // but we can't exceed maxtime * maxdps!
1147 totaldamage = min(maxdamage, maxtime * maxdps);
1151 // totaldamage = min(mindamage + d, maxtime * maxdps)
1153 // totaldamage <= maxtime * maxdps
1154 // ==> totaldamage / maxdps <= maxtime.
1156 // totaldamage / mindps = min(mindamage / mindps + d, maxtime * maxdps / mindps)
1157 // >= min(mintime, maxtime)
1158 // ==> totaldamage / maxdps >= mintime.
1161 // how long do we damage then?
1162 // at least as long as before
1163 // but, never exceed maxdps
1164 totaltime = max(mintime, totaldamage / maxdps); // always <= maxtime due to lemma
1168 // at most as long as maximum allowed
1169 // but, never below mindps
1170 totaltime = min(maxtime, totaldamage / mindps); // always >= mintime due to lemma
1172 // assuming t > mintime, dps > mindps:
1173 // we get d = t * dps = maxtime * maxdps
1174 // totaldamage = min(maxdamage, maxtime * maxdps) = min(... + d, maxtime * maxdps) = maxtime * maxdps
1175 // totaldamage / maxdps = maxtime
1176 // totaldamage / mindps > totaldamage / maxdps = maxtime
1178 // a) totaltime = max(mintime, maxtime) = maxtime
1179 // b) totaltime = min(maxtime, totaldamage / maxdps) = maxtime
1181 // assuming t <= mintime:
1182 // we get maxtime = mintime
1183 // a) totaltime = max(mintime, ...) >= mintime, also totaltime <= maxtime by the lemma, therefore totaltime = mintime = maxtime
1184 // b) totaltime = min(maxtime, ...) <= maxtime, also totaltime >= mintime by the lemma, therefore totaltime = mintime = maxtime
1186 // assuming dps <= mindps:
1187 // we get mindps = maxdps.
1188 // With this, the lemma says that mintime <= totaldamage / mindps = totaldamage / maxdps <= maxtime.
1189 // a) totaltime = max(mintime, totaldamage / maxdps) = totaldamage / maxdps
1190 // b) totaltime = min(maxtime, totaldamage / mindps) = totaldamage / maxdps
1192 e.fire_damagepersec = totaldamage / totaltime;
1193 e.fire_endtime = time + totaltime;
1194 if(totaldamage > 1.2 * mindamage)
1196 e.fire_deathtype = dt;
1197 if(e.fire_owner != o)
1200 e.fire_hitsound = false;
1203 if(accuracy_isgooddamage(o, e))
1204 accuracy_add(o, DEATH_WEAPONOF(dt), 0, max(0, totaldamage - mindamage));
1205 return max(0, totaldamage - mindamage); // can never be negative, but to make sure
1212 e.fire_damagepersec = dps;
1213 e.fire_endtime = time + t;
1214 e.fire_deathtype = dt;
1216 e.fire_hitsound = false;
1217 if(accuracy_isgooddamage(o, e))
1218 accuracy_add(o, DEATH_WEAPONOF(dt), 0, d);
1223 void Fire_ApplyDamage(entity e)
1228 if (!Fire_IsBurning(e))
1231 for(t = 0, o = e.owner; o.owner && t < 16; o = o.owner, ++t);
1232 if(IS_NOT_A_CLIENT(o))
1235 // water and slime stop fire
1237 if(e.watertype != CONTENT_LAVA)
1244 t = min(frametime, e.fire_endtime - time);
1245 d = e.fire_damagepersec * t;
1247 hi = e.fire_owner.damage_dealt;
1248 ty = e.fire_owner.typehitsound;
1249 Damage(e, e, e.fire_owner, d, e.fire_deathtype, DMG_NOWEP, e.origin, '0 0 0');
1250 if(e.fire_hitsound && e.fire_owner)
1252 e.fire_owner.damage_dealt = hi;
1253 e.fire_owner.typehitsound = ty;
1255 e.fire_hitsound = true;
1257 if(!IS_INDEPENDENT_PLAYER(e) && !STAT(FROZEN, e))
1259 IL_EACH(g_damagedbycontents, it.damagedbycontents && it != e,
1261 if(!IS_DEAD(it) && it.takedamage && !IS_INDEPENDENT_PLAYER(it))
1262 if(boxesoverlap(e.absmin, e.absmax, it.absmin, it.absmax))
1264 t = autocvar_g_balance_firetransfer_time * (e.fire_endtime - time);
1265 d = autocvar_g_balance_firetransfer_damage * e.fire_damagepersec * t;
1266 Fire_AddDamage(it, o, d, t, DEATH_FIRE.m_id);
1272 void Fire_ApplyEffect(entity e)
1274 if(Fire_IsBurning(e))
1275 e.effects |= EF_FLAME;
1277 e.effects &= ~EF_FLAME;
1280 void fireburner_think(entity this)
1282 // for players, this is done in the regular loop
1283 if(wasfreed(this.owner))
1288 Fire_ApplyEffect(this.owner);
1289 if(!Fire_IsBurning(this.owner))
1291 this.owner.fire_burner = NULL;
1295 Fire_ApplyDamage(this.owner);
1296 this.nextthink = time;