3 #include <common/effects/all.qh>
6 #include <server/gamelog.qh>
7 #include <server/items/items.qh>
8 #include <server/mutators/_mod.qh>
9 #include <server/sv_main.qh>
10 #include "teamplay.qh"
12 #include "spawnpoints.qh"
13 #include "../common/state.qh"
14 #include "../common/physics/player.qh"
15 #include "resources.qh"
16 #include "../common/vehicles/all.qh"
17 #include "../common/items/_mod.qh"
18 #include "../common/mutators/mutator/waypoints/waypointsprites.qh"
19 #include "../common/mutators/mutator/instagib/sv_instagib.qh"
20 #include "../common/mutators/mutator/buffs/buffs.qh"
21 #include "weapons/accuracy.qh"
22 #include "weapons/csqcprojectile.qh"
23 #include "weapons/selection.qh"
24 #include "../common/constants.qh"
25 #include "../common/deathtypes/all.qh"
26 #include <common/mapobjects/defs.qh>
27 #include "../common/notifications/all.qh"
28 #include "../common/physics/movetypes/movetypes.qh"
29 #include "../common/playerstats.qh"
30 #include "../common/teams.qh"
31 #include "../common/util.qh"
32 #include <common/gamemodes/_mod.qh>
33 #include <common/gamemodes/rules.qh>
34 #include <common/weapons/_all.qh>
35 #include "../lib/csqcmodel/sv_model.qh"
36 #include "../lib/warpzone/common.qh"
38 void UpdateFrags(entity player, int f)
40 GameRules_scoring_add_team(player, SCORE, f);
43 void GiveFrags(entity attacker, entity targ, float f, int deathtype, .entity weaponentity)
45 // TODO route through PlayerScores instead
46 if(game_stopped) return;
53 GameRules_scoring_add(attacker, SUICIDES, 1);
58 GameRules_scoring_add(attacker, TEAMKILLS, 1);
64 GameRules_scoring_add(attacker, KILLS, 1);
65 if(!warmup_stage && targ.playerid)
66 PlayerStats_GameReport_Event_Player(attacker, sprintf("kills-%d", targ.playerid), 1);
69 GameRules_scoring_add(targ, DEATHS, 1);
71 // FIXME fix the mess this is (we have REAL points now!)
72 if(MUTATOR_CALLHOOK(GiveFragsForKill, attacker, targ, f, deathtype, attacker.(weaponentity)))
75 attacker.totalfrags += f;
78 UpdateFrags(attacker, f);
81 string AppendItemcodes(string s, entity player)
83 for(int slot = 0; slot < MAX_WEAPONSLOTS; ++slot)
85 .entity weaponentity = weaponentities[slot];
86 int w = player.(weaponentity).m_weapon.m_id;
88 w = player.(weaponentity).cnt; // previous weapon
89 if(w != 0 || slot == 0)
90 s = strcat(s, ftos(w));
92 if(time < STAT(STRENGTH_FINISHED, player))
94 if(time < STAT(INVINCIBLE_FINISHED, player))
96 if(PHYS_INPUT_BUTTON_CHAT(player))
98 // TODO: include these codes as a flag on the item itself
99 MUTATOR_CALLHOOK(LogDeath_AppendItemCodes, player, s);
100 s = M_ARGV(1, string);
104 void LogDeath(string mode, int deathtype, entity killer, entity killed)
107 if(!autocvar_sv_eventlog)
109 s = strcat(":kill:", mode);
110 s = strcat(s, ":", ftos(killer.playerid));
111 s = strcat(s, ":", ftos(killed.playerid));
112 s = strcat(s, ":type=", Deathtype_Name(deathtype));
113 s = strcat(s, ":items=");
114 s = AppendItemcodes(s, killer);
117 s = strcat(s, ":victimitems=");
118 s = AppendItemcodes(s, killed);
123 void Obituary_SpecialDeath(
127 string s1, string s2, string s3,
128 float f1, float f2, float f3)
130 if(!DEATH_ISSPECIAL(deathtype))
132 backtrace("Obituary_SpecialDeath called without a special deathtype?\n");
136 entity deathent = REGISTRY_GET(Deathtypes, deathtype - DT_FIRST);
139 backtrace("Obituary_SpecialDeath: Could not find deathtype entity!\n");
143 if(g_cts && deathtype == DEATH_KILL.m_id)
144 return; // TODO: somehow put this in CTS gamemode file!
146 Notification death_message = (murder) ? deathent.death_msgmurder : deathent.death_msgself;
149 Send_Notification_WOCOVA(
157 Send_Notification_WOCOVA(
161 death_message.nent_msginfo,
168 float Obituary_WeaponDeath(
172 string s1, string s2, string s3,
175 Weapon death_weapon = DEATH_WEAPONOF(deathtype);
176 if (death_weapon == WEP_Null)
179 w_deathtype = deathtype;
180 Notification death_message = ((murder) ? death_weapon.wr_killmessage(death_weapon) : death_weapon.wr_suicidemessage(death_weapon));
185 Send_Notification_WOCOVA(
193 // send the info part to everyone
194 Send_Notification_WOCOVA(
198 death_message.nent_msginfo,
206 "Obituary_WeaponDeath(): ^1Deathtype ^7(%d)^1 has no notification for weapon %s!\n",
215 bool frag_centermessage_override(entity attacker, entity targ, int deathtype, int kill_count_to_attacker, int kill_count_to_target, string attacker_name)
217 if(deathtype == DEATH_FIRE.m_id)
219 Send_Notification(NOTIF_ONE, attacker, MSG_CHOICE, CHOICE_FRAG_FIRE, targ.netname, kill_count_to_attacker, (IS_BOT_CLIENT(targ) ? -1 : CS(targ).ping));
220 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));
224 return MUTATOR_CALLHOOK(FragCenterMessage, attacker, targ, deathtype, kill_count_to_attacker, kill_count_to_target);
227 void Obituary(entity attacker, entity inflictor, entity targ, int deathtype, .entity weaponentity)
230 if (!IS_PLAYER(targ)) { backtrace("Obituary called on non-player?!\n"); return; }
233 float notif_firstblood = false;
234 float kill_count_to_attacker, kill_count_to_target;
235 bool notif_anonymous = false;
236 string attacker_name = attacker.netname;
238 // Set final information for the death
239 targ.death_origin = targ.origin;
240 string deathlocation = (autocvar_notification_server_allows_location ? NearestLocation(targ.death_origin) : "");
242 // Abort now if a mutator requests it
243 if (MUTATOR_CALLHOOK(ClientObituary, inflictor, attacker, targ, deathtype, attacker.(weaponentity))) { CS(targ).killcount = 0; return; }
244 notif_anonymous = M_ARGV(5, bool);
247 attacker_name = "Anonymous player";
249 #ifdef NOTIFICATIONS_DEBUG
252 "Obituary(%s, %s, %s, %s = %d);\n",
256 Deathtype_Name(deathtype),
267 if(DEATH_ISSPECIAL(deathtype))
269 if(deathtype == DEATH_TEAMCHANGE.m_id || deathtype == DEATH_AUTOTEAMCHANGE.m_id)
271 Obituary_SpecialDeath(targ, false, deathtype, targ.netname, deathlocation, "", targ.team, 0, 0);
275 switch(DEATH_ENT(deathtype))
277 case DEATH_MIRRORDAMAGE:
279 Obituary_SpecialDeath(targ, false, deathtype, targ.netname, deathlocation, "", CS(targ).killcount, 0, 0);
285 Obituary_SpecialDeath(targ, false, deathtype, targ.netname, deathlocation, "", CS(targ).killcount, 0, 0);
291 else if (!Obituary_WeaponDeath(targ, false, deathtype, targ.netname, deathlocation, "", CS(targ).killcount, 0))
293 backtrace("SUICIDE: what the hell happened here?\n");
296 LogDeath("suicide", deathtype, targ, targ);
297 if(deathtype != DEATH_AUTOTEAMCHANGE.m_id) // special case: don't negate frags if auto switched
298 GiveFrags(attacker, targ, -1, deathtype, weaponentity);
304 else if(IS_PLAYER(attacker))
306 if(SAME_TEAM(attacker, targ))
308 LogDeath("tk", deathtype, attacker, targ);
309 GiveFrags(attacker, targ, -1, deathtype, weaponentity);
311 CS(attacker).killcount = 0;
313 Send_Notification(NOTIF_ONE, attacker, MSG_CENTER, CENTER_DEATH_TEAMKILL_FRAG, targ.netname);
314 Send_Notification(NOTIF_ONE, targ, MSG_CENTER, CENTER_DEATH_TEAMKILL_FRAGGED, attacker_name);
315 Send_Notification(NOTIF_ALL, NULL, MSG_INFO, APP_TEAM_NUM(targ.team, INFO_DEATH_TEAMKILL), targ.netname, attacker_name, deathlocation, CS(targ).killcount);
317 // In this case, the death message will ALWAYS be "foo was betrayed by bar"
318 // No need for specific death/weapon messages...
322 LogDeath("frag", deathtype, attacker, targ);
323 GiveFrags(attacker, targ, 1, deathtype, weaponentity);
325 CS(attacker).taunt_soundtime = time + 1;
326 CS(attacker).killcount = CS(attacker).killcount + 1;
328 attacker.killsound += 1;
330 // TODO: improve SPREE_ITEM and KILL_SPREE_LIST
331 // these 2 macros are spread over multiple files
332 #define SPREE_ITEM(counta,countb,center,normal,gentle) \
334 Send_Notification(NOTIF_ONE, attacker, MSG_ANNCE, ANNCE_KILLSTREAK_##countb); \
336 PlayerStats_GameReport_Event_Player(attacker, PLAYERSTATS_ACHIEVEMENT_KILL_SPREE_##counta, 1); \
339 switch(CS(attacker).killcount)
346 if(!warmup_stage && !checkrules_firstblood)
348 checkrules_firstblood = true;
349 notif_firstblood = true; // modify the current messages so that they too show firstblood information
350 PlayerStats_GameReport_Event_Player(attacker, PLAYERSTATS_ACHIEVEMENT_FIRSTBLOOD, 1);
351 PlayerStats_GameReport_Event_Player(targ, PLAYERSTATS_ACHIEVEMENT_FIRSTVICTIM, 1);
353 // tell spree_inf and spree_cen that this is a first-blood and first-victim event
354 kill_count_to_attacker = -1;
355 kill_count_to_target = -2;
359 kill_count_to_attacker = CS(attacker).killcount;
360 kill_count_to_target = 0;
371 kill_count_to_attacker,
372 (IS_BOT_CLIENT(targ) ? -1 : CS(targ).ping)
380 kill_count_to_target,
381 GetResource(attacker, RES_HEALTH),
382 GetResource(attacker, RES_ARMOR),
383 (IS_BOT_CLIENT(attacker) ? -1 : CS(attacker).ping)
386 else if(!frag_centermessage_override(attacker, targ, deathtype, kill_count_to_attacker, kill_count_to_target, attacker_name))
394 kill_count_to_attacker,
395 (IS_BOT_CLIENT(targ) ? -1 : CS(targ).ping)
403 kill_count_to_target,
404 GetResource(attacker, RES_HEALTH),
405 GetResource(attacker, RES_ARMOR),
406 (IS_BOT_CLIENT(attacker) ? -1 : CS(attacker).ping)
411 if(deathtype == DEATH_BUFF.m_id)
412 f3 = buff_FirstFromFlags(STAT(BUFFS, attacker)).m_id;
414 if (!Obituary_WeaponDeath(targ, true, deathtype, targ.netname, attacker_name, deathlocation, CS(targ).killcount, kill_count_to_attacker))
415 Obituary_SpecialDeath(targ, true, deathtype, targ.netname, attacker_name, deathlocation, CS(targ).killcount, kill_count_to_attacker, f3);
424 switch(DEATH_ENT(deathtype))
426 // For now, we're just forcing HURTTRIGGER to behave as "DEATH_VOID" and giving it no special options...
427 // Later on you will only be able to make custom messages using DEATH_CUSTOM,
428 // and there will be a REAL DEATH_VOID implementation which mappers will use.
429 case DEATH_HURTTRIGGER:
431 Obituary_SpecialDeath(targ, false, deathtype,
443 Obituary_SpecialDeath(targ, false, deathtype,
445 ((strstrofs(deathmessage, "%", 0) < 0) ? strcat("%s ", deathmessage) : deathmessage),
455 Obituary_SpecialDeath(targ, false, deathtype, targ.netname, deathlocation, "", CS(targ).killcount, 0, 0);
460 LogDeath("accident", deathtype, targ, targ);
461 GiveFrags(targ, targ, -1, deathtype, weaponentity);
463 if(GameRules_scoring_add(targ, SCORE, 0) == -5)
465 Send_Notification(NOTIF_ONE, targ, MSG_ANNCE, ANNCE_ACHIEVEMENT_BOTLIKE);
468 PlayerStats_GameReport_Event_Player(attacker, PLAYERSTATS_ACHIEVEMENT_BOTLIKE, 1);
473 // reset target kill count
474 CS(targ).killcount = 0;
477 void Ice_Think(entity this)
479 if(!STAT(FROZEN, this.owner) || this.owner.iceblock != this)
484 vector ice_org = this.owner.origin - '0 0 16';
485 if (this.origin != ice_org)
486 setorigin(this, ice_org);
487 this.nextthink = time;
490 void Freeze(entity targ, float revivespeed, int frozen_type, bool show_waypoint)
492 if(!IS_PLAYER(targ) && !IS_MONSTER(targ)) // TODO: only specified entities can be freezed
495 if(STAT(FROZEN, targ))
498 float targ_maxhealth = ((IS_MONSTER(targ)) ? targ.max_health : start_health);
500 STAT(FROZEN, targ) = frozen_type;
501 STAT(REVIVE_PROGRESS, targ) = ((frozen_type == FROZEN_TEMP_DYING) ? 1 : 0);
502 SetResource(targ, RES_HEALTH, ((frozen_type == FROZEN_TEMP_DYING) ? targ_maxhealth : 1));
503 targ.revive_speed = revivespeed;
505 IL_REMOVE(g_bot_targets, targ);
506 targ.bot_attack = false;
507 targ.freeze_time = time;
509 entity ice = new(ice);
511 ice.scale = targ.scale;
512 // set_movetype(ice, MOVETYPE_FOLLOW) would rotate the ice model with the player
513 setthink(ice, Ice_Think);
514 ice.nextthink = time;
515 ice.frame = floor(random() * 21); // ice model has 20 different looking frames
516 setmodel(ice, MDL_ICE);
518 ice.colormod = Team_ColorRGB(targ.team);
519 ice.glowmod = ice.colormod;
521 targ.revival_time = 0;
525 RemoveGrapplingHooks(targ);
527 FOREACH_CLIENT(IS_PLAYER(it),
529 for(int slot = 0; slot < MAX_WEAPONSLOTS; ++slot)
531 .entity weaponentity = weaponentities[slot];
532 if(it.(weaponentity).hook.aiment == targ)
533 RemoveHook(it.(weaponentity).hook);
538 if(MUTATOR_CALLHOOK(Freeze, targ, revivespeed, frozen_type) || show_waypoint)
539 WaypointSprite_Spawn(WP_Frozen, 0, 0, targ, '0 0 64', NULL, targ.team, targ, waypointsprite_attached, true, RADARICON_WAYPOINT);
542 void Unfreeze(entity targ, bool reset_health)
544 if(!STAT(FROZEN, targ))
547 if (reset_health && STAT(FROZEN, targ) != FROZEN_TEMP_DYING)
548 SetResource(targ, RES_HEALTH, ((IS_PLAYER(targ)) ? start_health : targ.max_health));
550 targ.pauseregen_finished = time + autocvar_g_balance_pause_health_regen;
552 STAT(FROZEN, targ) = 0;
553 STAT(REVIVE_PROGRESS, targ) = 0;
554 targ.revival_time = time;
556 IL_PUSH(g_bot_targets, targ);
557 targ.bot_attack = true;
559 WaypointSprite_Kill(targ.waypointsprite_attached);
561 FOREACH_CLIENT(IS_PLAYER(it),
563 for(int slot = 0; slot < MAX_WEAPONSLOTS; ++slot)
565 .entity weaponentity = weaponentities[slot];
566 if(it.(weaponentity).hook.aiment == targ)
567 RemoveHook(it.(weaponentity).hook);
571 // remove the ice block
573 delete(targ.iceblock);
574 targ.iceblock = NULL;
576 MUTATOR_CALLHOOK(Unfreeze, targ);
579 void Damage(entity targ, entity inflictor, entity attacker, float damage, int deathtype, .entity weaponentity, vector hitloc, vector force)
581 float complainteamdamage = 0;
582 float mirrordamage = 0;
583 float mirrorforce = 0;
585 if (game_stopped || (IS_CLIENT(targ) && CS(targ).killcount == FRAGS_SPECTATOR))
588 entity attacker_save = attacker;
590 // special rule: gravity bombs and sound-based attacks do not affect team mates (other than for disconnecting the hook)
591 if(DEATH_ISWEAPON(deathtype, WEP_HOOK) || (deathtype & HITTYPE_SOUND))
593 if(IS_PLAYER(targ) && SAME_TEAM(targ, attacker))
599 if(deathtype == DEATH_KILL.m_id || deathtype == DEATH_TEAMCHANGE.m_id || deathtype == DEATH_AUTOTEAMCHANGE.m_id)
601 // exit the vehicle before killing (fixes a crash)
602 if(IS_PLAYER(targ) && targ.vehicle)
603 vehicles_exit(targ.vehicle, VHEF_RELEASE);
605 // These are ALWAYS lethal
606 // No damage modification here
607 // Instead, prepare the victim for his death...
608 SetResourceExplicit(targ, RES_ARMOR, 0);
609 targ.spawnshieldtime = 0;
610 SetResourceExplicit(targ, RES_HEALTH, 0.9); // this is < 1
611 targ.flags -= targ.flags & FL_GODMODE;
614 else if(deathtype == DEATH_MIRRORDAMAGE.m_id || deathtype == DEATH_NOAMMO.m_id)
620 // nullify damage if teamplay is on
621 if(deathtype != DEATH_TELEFRAG.m_id)
622 if(IS_PLAYER(attacker))
624 if(IS_PLAYER(targ) && targ != attacker && (IS_INDEPENDENT_PLAYER(attacker) || IS_INDEPENDENT_PLAYER(targ)))
629 else if(SAME_TEAM(attacker, targ))
631 if(autocvar_teamplay_mode == 1)
633 else if(attacker != targ)
635 if(autocvar_teamplay_mode == 2)
637 if(IS_PLAYER(targ) && !IS_DEAD(targ))
639 attacker.dmg_team = attacker.dmg_team + damage;
640 complainteamdamage = attacker.dmg_team - autocvar_g_teamdamage_threshold;
643 else if(autocvar_teamplay_mode == 3)
645 else if(autocvar_teamplay_mode == 4)
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;
651 if(complainteamdamage > 0)
652 mirrordamage = autocvar_g_mirrordamage * complainteamdamage;
653 mirrorforce = autocvar_g_mirrordamage * vlen(force);
654 damage = autocvar_g_friendlyfire * damage;
655 // mirrordamage will be used LATER
657 if(autocvar_g_mirrordamage_virtual)
659 vector v = healtharmor_applydamage(GetResource(attacker, RES_ARMOR), autocvar_g_balance_armor_blockpercent, deathtype, mirrordamage);
660 attacker.dmg_take += v.x;
661 attacker.dmg_save += v.y;
662 attacker.dmg_inflictor = inflictor;
667 if(autocvar_g_friendlyfire_virtual)
669 vector v = healtharmor_applydamage(GetResource(targ, RES_ARMOR), autocvar_g_balance_armor_blockpercent, deathtype, damage);
670 targ.dmg_take += v.x;
671 targ.dmg_save += v.y;
672 targ.dmg_inflictor = inflictor;
674 if(!autocvar_g_friendlyfire_virtual_force)
678 else if(!targ.canteamdamage)
685 if (!DEATH_ISSPECIAL(deathtype))
687 damage *= g_weapondamagefactor;
688 mirrordamage *= g_weapondamagefactor;
689 complainteamdamage *= g_weapondamagefactor;
690 force = force * g_weaponforcefactor;
691 mirrorforce *= g_weaponforcefactor;
694 // should this be changed at all? If so, in what way?
695 MUTATOR_CALLHOOK(Damage_Calculate, inflictor, attacker, targ, deathtype, damage, mirrordamage, force, attacker.(weaponentity));
696 damage = M_ARGV(4, float);
697 mirrordamage = M_ARGV(5, float);
698 force = M_ARGV(6, vector);
700 if(IS_PLAYER(targ) && damage > 0 && attacker)
702 for(int slot = 0; slot < MAX_WEAPONSLOTS; ++slot)
704 .entity went = weaponentities[slot];
705 if(targ.(went).hook && targ.(went).hook.aiment == attacker)
706 RemoveHook(targ.(went).hook);
710 if(STAT(FROZEN, targ) && !ITEM_DAMAGE_NEEDKILL(deathtype)
711 && deathtype != DEATH_TEAMCHANGE.m_id && deathtype != DEATH_AUTOTEAMCHANGE.m_id)
713 if(autocvar_g_frozen_revive_falldamage > 0 && deathtype == DEATH_FALL.m_id && damage >= autocvar_g_frozen_revive_falldamage)
715 Unfreeze(targ, false);
716 SetResource(targ, RES_HEALTH, autocvar_g_frozen_revive_falldamage_health);
717 Send_Effect(EFFECT_ICEORGLASS, targ.origin, '0 0 0', 3);
718 Send_Notification(NOTIF_ALL, NULL, MSG_INFO, INFO_FREEZETAG_REVIVED_FALL, targ.netname);
719 Send_Notification(NOTIF_ONE, targ, MSG_CENTER, CENTER_FREEZETAG_REVIVE_SELF);
723 force *= autocvar_g_frozen_force;
726 if(IS_PLAYER(targ) && STAT(FROZEN, targ)
727 && ITEM_DAMAGE_NEEDKILL(deathtype) && !autocvar_g_frozen_damage_trigger)
729 Send_Effect(EFFECT_TELEPORT, targ.origin, '0 0 0', 1);
731 entity spot = SelectSpawnPoint(targ, false);
735 targ.deadflag = DEAD_NO;
737 targ.angles = spot.angles;
740 targ.effects |= EF_TELEPORT_BIT;
742 targ.angles_z = 0; // never spawn tilted even if the spot says to
743 targ.fixangle = true; // turn this way immediately
744 targ.velocity = '0 0 0';
745 targ.avelocity = '0 0 0';
746 targ.punchangle = '0 0 0';
747 targ.punchvector = '0 0 0';
748 targ.oldvelocity = targ.velocity;
750 targ.spawnorigin = spot.origin;
751 setorigin(targ, spot.origin + '0 0 1' * (1 - targ.mins.z - 24));
752 // don't reset back to last position, even if new position is stuck in solid
753 targ.oldorigin = targ.origin;
755 Send_Effect(EFFECT_TELEPORT, targ.origin, '0 0 0', 1);
759 if(!MUTATOR_IS_ENABLED(mutator_instagib))
761 // apply strength multiplier
762 if (attacker.items & ITEM_Strength.m_itemid)
766 damage = damage * autocvar_g_balance_powerup_strength_selfdamage;
767 force = force * autocvar_g_balance_powerup_strength_selfforce;
771 damage = damage * autocvar_g_balance_powerup_strength_damage;
772 force = force * autocvar_g_balance_powerup_strength_force;
776 // apply invincibility multiplier
777 if (targ.items & ITEM_Shield.m_itemid)
779 damage = damage * autocvar_g_balance_powerup_invincible_takedamage;
780 if (targ != attacker)
782 force = force * autocvar_g_balance_powerup_invincible_takeforce;
787 if (targ == attacker)
788 damage = damage * autocvar_g_balance_selfdamagepercent; // Partial damage if the attacker hits himself
793 if(deathtype != DEATH_BUFF.m_id)
794 if(targ.takedamage == DAMAGE_AIM)
798 if(IS_VEHICLE(targ) && targ.owner)
803 if(IS_PLAYER(victim) || (IS_TURRET(victim) && victim.active == ACTIVE_ACTIVE) || IS_MONSTER(victim) || MUTATOR_CALLHOOK(PlayHitsound, victim, attacker))
805 if(DIFF_TEAM(victim, attacker) && !STAT(FROZEN, victim))
809 if(deathtype != DEATH_FIRE.m_id)
811 if(PHYS_INPUT_BUTTON_CHAT(victim))
812 attacker.typehitsound += 1;
814 attacker.damage_dealt += damage;
817 damage_goodhits += 1;
818 damage_gooddamage += damage;
820 if (!DEATH_ISSPECIAL(deathtype))
822 if(IS_PLAYER(targ)) // don't do this for vehicles
828 else if(IS_PLAYER(attacker))
830 // if enemy gets frozen in this frame and receives other damage don't
831 // play the typehitsound e.g. when hit by multiple bullets of the shotgun
832 if (deathtype != DEATH_FIRE.m_id && (!STAT(FROZEN, victim) || time > victim.freeze_time))
834 attacker.typehitsound += 1;
836 if(complainteamdamage > 0)
837 if(time > CS(attacker).teamkill_complain)
839 CS(attacker).teamkill_complain = time + 5;
840 CS(attacker).teamkill_soundtime = time + 0.4;
841 CS(attacker).teamkill_soundsource = targ;
849 if (targ.damageforcescale)
851 if (!IS_PLAYER(targ) || time >= targ.spawnshieldtime || targ == attacker)
853 vector farce = damage_explosion_calcpush(targ.damageforcescale * force, targ.velocity, autocvar_g_balance_damagepush_speedfactor);
854 if(targ.move_movetype == MOVETYPE_PHYSICS)
856 entity farcent = new(farce);
857 farcent.enemy = targ;
858 farcent.movedir = farce * 10;
860 farcent.movedir = farcent.movedir * targ.mass;
861 farcent.origin = hitloc;
862 farcent.forcetype = FORCETYPE_FORCEATPOS;
863 farcent.nextthink = time + 0.1;
864 setthink(farcent, SUB_Remove);
866 else if(targ.move_movetype != MOVETYPE_NOCLIP)
868 targ.velocity = targ.velocity + farce;
870 UNSET_ONGROUND(targ);
871 UpdateCSQCProjectile(targ);
874 if (damage != 0 || (targ.damageforcescale && force))
875 if (targ.event_damage)
876 targ.event_damage (targ, inflictor, attacker, damage, deathtype, weaponentity, hitloc, force);
878 // apply mirror damage if any
879 if(!autocvar_g_mirrordamage_onlyweapons || DEATH_WEAPONOF(deathtype) != WEP_Null)
880 if(mirrordamage > 0 || mirrorforce > 0)
882 attacker = attacker_save;
884 force = normalize(attacker.origin + attacker.view_ofs - hitloc) * mirrorforce;
885 Damage(attacker, inflictor, attacker, mirrordamage, DEATH_MIRRORDAMAGE.m_id, weaponentity, attacker.origin, force);
889 float RadiusDamageForSource (entity inflictor, vector inflictororigin, vector inflictorvelocity, entity attacker, float coredamage, float edgedamage, float rad, entity cantbe, entity mustbe,
890 float inflictorselfdamage, float forceintensity, float forcezscale, int deathtype, .entity weaponentity, entity directhitentity)
891 // Returns total damage applies to creatures
895 float total_damage_to_creatures;
900 float stat_damagedone;
902 if(RadiusDamage_running)
904 backtrace("RadiusDamage called recursively! Expect stuff to go HORRIBLY wrong.");
908 RadiusDamage_running = 1;
910 tfloordmg = autocvar_g_throughfloor_damage;
911 tfloorforce = autocvar_g_throughfloor_force;
913 total_damage_to_creatures = 0;
915 if(deathtype != (WEP_HOOK.m_id | HITTYPE_SECONDARY | HITTYPE_BOUNCE)) // only send gravity bomb damage once
916 if(!(deathtype & HITTYPE_SOUND)) // do not send radial sound damage (bandwidth hog)
918 force = inflictorvelocity;
922 force = normalize(force);
923 if(forceintensity >= 0)
924 Damage_DamageInfo(inflictororigin, coredamage, edgedamage, rad, forceintensity * force, deathtype, 0, attacker);
926 Damage_DamageInfo(inflictororigin, coredamage, edgedamage, -rad, (-forceintensity) * force, deathtype, 0, attacker);
931 targ = WarpZone_FindRadius (inflictororigin, rad + MAX_DAMAGEEXTRARADIUS, false);
935 if ((targ != inflictor) || inflictorselfdamage)
936 if (((cantbe != targ) && !mustbe) || (mustbe == targ))
943 // LordHavoc: measure distance to nearest point on target (not origin)
944 // (this guarentees 100% damage on a touch impact)
945 nearest = targ.WarpZone_findradius_nearest;
946 diff = targ.WarpZone_findradius_dist;
947 // round up a little on the damage to ensure full damage on impacts
948 // and turn the distance into a fraction of the radius
949 power = 1 - ((vlen (diff) - bound(MIN_DAMAGEEXTRARADIUS, targ.damageextraradius, MAX_DAMAGEEXTRARADIUS)) / rad);
951 //bprint(ftos(power));
952 //if (targ == attacker)
953 // print(ftos(power), "\n");
959 finaldmg = coredamage * power + edgedamage * (1 - power);
965 vector myblastorigin;
968 myblastorigin = WarpZone_TransformOrigin(targ, inflictororigin);
970 // if it's a player, use the view origin as reference
971 center = CENTER_OR_VIEWOFS(targ);
973 force = normalize(center - myblastorigin);
974 force = force * (finaldmg / coredamage) * forceintensity;
977 // apply special scaling along the z axis if set
978 // NOTE: 0 value is not allowed for compatibility, in the case of weapon cvars not being set
980 force.z *= forcezscale;
982 if(targ != directhitentity)
987 float mininv_f, mininv_d;
989 // test line of sight to multiple positions on box,
990 // and do damage if any of them hit
993 // we know: max stddev of hitratio = 1 / (2 * sqrt(n))
994 // so for a given max stddev:
995 // n = (1 / (2 * max stddev of hitratio))^2
997 mininv_d = (finaldmg * (1-tfloordmg)) / autocvar_g_throughfloor_damage_max_stddev;
998 mininv_f = (vlen(force) * (1-tfloorforce)) / autocvar_g_throughfloor_force_max_stddev;
1000 if(autocvar_g_throughfloor_debug)
1001 LOG_INFOF("THROUGHFLOOR: D=%f F=%f max(dD)=1/%f max(dF)=1/%f", finaldmg, vlen(force), mininv_d, mininv_f);
1004 total = 0.25 * (max(mininv_f, mininv_d) ** 2);
1006 if(autocvar_g_throughfloor_debug)
1007 LOG_INFOF(" steps=%f", total);
1010 if (IS_PLAYER(targ))
1011 total = ceil(bound(autocvar_g_throughfloor_min_steps_player, total, autocvar_g_throughfloor_max_steps_player));
1013 total = ceil(bound(autocvar_g_throughfloor_min_steps_other, total, autocvar_g_throughfloor_max_steps_other));
1015 if(autocvar_g_throughfloor_debug)
1016 LOG_INFOF(" steps=%f dD=%f dF=%f", total, finaldmg * (1-tfloordmg) / (2 * sqrt(total)), vlen(force) * (1-tfloorforce) / (2 * sqrt(total)));
1018 for(c = 0; c < total; ++c)
1020 //traceline(targ.WarpZone_findradius_findorigin, nearest, MOVE_NOMONSTERS, inflictor);
1021 WarpZone_TraceLine(inflictororigin, WarpZone_UnTransformOrigin(targ, nearest), MOVE_NOMONSTERS, inflictor);
1022 if (trace_fraction == 1 || trace_ent == targ)
1026 hitloc = hitloc + nearest;
1030 nearest.x = targ.origin.x + targ.mins.x + random() * targ.size.x;
1031 nearest.y = targ.origin.y + targ.mins.y + random() * targ.size.y;
1032 nearest.z = targ.origin.z + targ.mins.z + random() * targ.size.z;
1035 nearest = hitloc * (1 / max(1, hits));
1036 hitratio = (hits / total);
1037 a = bound(0, tfloordmg + (1-tfloordmg) * hitratio, 1);
1038 finaldmg = finaldmg * a;
1039 a = bound(0, tfloorforce + (1-tfloorforce) * hitratio, 1);
1042 if(autocvar_g_throughfloor_debug)
1043 LOG_INFOF(" D=%f F=%f", finaldmg, vlen(force));
1046 //if (targ == attacker)
1048 // print("hits ", ftos(hits), " / ", ftos(total));
1049 // print(" finaldmg ", ftos(finaldmg), " force ", vtos(force));
1050 // print(" (", ftos(a), ")\n");
1052 if(finaldmg || force)
1056 total_damage_to_creatures += finaldmg;
1058 if(accuracy_isgooddamage(attacker, targ))
1059 stat_damagedone += finaldmg;
1062 if(targ == directhitentity || DEATH_ISSPECIAL(deathtype))
1063 Damage(targ, inflictor, attacker, finaldmg, deathtype, weaponentity, nearest, force);
1065 Damage(targ, inflictor, attacker, finaldmg, deathtype | HITTYPE_SPLASH, weaponentity, nearest, force);
1073 RadiusDamage_running = 0;
1075 if(!DEATH_ISSPECIAL(deathtype))
1076 accuracy_add(attacker, DEATH_WEAPONOF(deathtype), 0, min(coredamage, stat_damagedone));
1078 return total_damage_to_creatures;
1081 float RadiusDamage(entity inflictor, entity attacker, float coredamage, float edgedamage, float rad, entity cantbe, entity mustbe, float forceintensity, int deathtype, .entity weaponentity, entity directhitentity)
1083 return RadiusDamageForSource(inflictor, (inflictor.origin + (inflictor.mins + inflictor.maxs) * 0.5), inflictor.velocity, attacker, coredamage, edgedamage, rad,
1084 cantbe, mustbe, false, forceintensity, 1, deathtype, weaponentity, directhitentity);
1087 bool Heal(entity targ, entity inflictor, float amount, float limit)
1089 if(game_stopped || (IS_CLIENT(targ) && CS(targ).killcount == FRAGS_SPECTATOR) || STAT(FROZEN, targ) || IS_DEAD(targ))
1092 bool healed = false;
1094 healed = targ.event_heal(targ, inflictor, amount, limit);
1095 // TODO: additional handling? what if the healing kills them? should this abort if healing would do so etc
1096 // TODO: healing fx!
1097 // TODO: armor healing?
1101 float Fire_IsBurning(entity e)
1103 return (time < e.fire_endtime);
1106 float Fire_AddDamage(entity e, entity o, float d, float t, float dt)
1109 float maxtime, mintime, maxdamage, mindamage, maxdps, mindps, totaldamage, totaltime;
1120 // print("adding a fire burner to ", e.classname, "\n");
1121 e.fire_burner = new(fireburner);
1122 setthink(e.fire_burner, fireburner_think);
1123 e.fire_burner.nextthink = time;
1124 e.fire_burner.owner = e;
1130 if(Fire_IsBurning(e))
1132 mintime = e.fire_endtime - time;
1133 maxtime = max(mintime, t);
1135 mindps = e.fire_damagepersec;
1136 maxdps = max(mindps, dps);
1138 if(maxtime > mintime || maxdps > mindps)
1142 // damage we have right now
1143 mindamage = mindps * mintime;
1145 // damage we want to get
1146 maxdamage = mindamage + d;
1148 // but we can't exceed maxtime * maxdps!
1149 totaldamage = min(maxdamage, maxtime * maxdps);
1153 // totaldamage = min(mindamage + d, maxtime * maxdps)
1155 // totaldamage <= maxtime * maxdps
1156 // ==> totaldamage / maxdps <= maxtime.
1158 // totaldamage / mindps = min(mindamage / mindps + d, maxtime * maxdps / mindps)
1159 // >= min(mintime, maxtime)
1160 // ==> totaldamage / maxdps >= mintime.
1163 // how long do we damage then?
1164 // at least as long as before
1165 // but, never exceed maxdps
1166 totaltime = max(mintime, totaldamage / maxdps); // always <= maxtime due to lemma
1170 // at most as long as maximum allowed
1171 // but, never below mindps
1172 totaltime = min(maxtime, totaldamage / mindps); // always >= mintime due to lemma
1174 // assuming t > mintime, dps > mindps:
1175 // we get d = t * dps = maxtime * maxdps
1176 // totaldamage = min(maxdamage, maxtime * maxdps) = min(... + d, maxtime * maxdps) = maxtime * maxdps
1177 // totaldamage / maxdps = maxtime
1178 // totaldamage / mindps > totaldamage / maxdps = maxtime
1180 // a) totaltime = max(mintime, maxtime) = maxtime
1181 // b) totaltime = min(maxtime, totaldamage / maxdps) = maxtime
1183 // assuming t <= mintime:
1184 // we get maxtime = mintime
1185 // a) totaltime = max(mintime, ...) >= mintime, also totaltime <= maxtime by the lemma, therefore totaltime = mintime = maxtime
1186 // b) totaltime = min(maxtime, ...) <= maxtime, also totaltime >= mintime by the lemma, therefore totaltime = mintime = maxtime
1188 // assuming dps <= mindps:
1189 // we get mindps = maxdps.
1190 // With this, the lemma says that mintime <= totaldamage / mindps = totaldamage / maxdps <= maxtime.
1191 // a) totaltime = max(mintime, totaldamage / maxdps) = totaldamage / maxdps
1192 // b) totaltime = min(maxtime, totaldamage / mindps) = totaldamage / maxdps
1194 e.fire_damagepersec = totaldamage / totaltime;
1195 e.fire_endtime = time + totaltime;
1196 if(totaldamage > 1.2 * mindamage)
1198 e.fire_deathtype = dt;
1199 if(e.fire_owner != o)
1202 e.fire_hitsound = false;
1205 if(accuracy_isgooddamage(o, e))
1206 accuracy_add(o, DEATH_WEAPONOF(dt), 0, max(0, totaldamage - mindamage));
1207 return max(0, totaldamage - mindamage); // can never be negative, but to make sure
1214 e.fire_damagepersec = dps;
1215 e.fire_endtime = time + t;
1216 e.fire_deathtype = dt;
1218 e.fire_hitsound = false;
1219 if(accuracy_isgooddamage(o, e))
1220 accuracy_add(o, DEATH_WEAPONOF(dt), 0, d);
1225 void Fire_ApplyDamage(entity e)
1230 if (!Fire_IsBurning(e))
1233 for(t = 0, o = e.owner; o.owner && t < 16; o = o.owner, ++t);
1234 if(IS_NOT_A_CLIENT(o))
1237 // water and slime stop fire
1239 if(e.watertype != CONTENT_LAVA)
1246 t = min(frametime, e.fire_endtime - time);
1247 d = e.fire_damagepersec * t;
1249 hi = e.fire_owner.damage_dealt;
1250 ty = e.fire_owner.typehitsound;
1251 Damage(e, e, e.fire_owner, d, e.fire_deathtype, DMG_NOWEP, e.origin, '0 0 0');
1252 if(e.fire_hitsound && e.fire_owner)
1254 e.fire_owner.damage_dealt = hi;
1255 e.fire_owner.typehitsound = ty;
1257 e.fire_hitsound = true;
1259 if(!IS_INDEPENDENT_PLAYER(e) && !STAT(FROZEN, e))
1261 IL_EACH(g_damagedbycontents, it.damagedbycontents && it != e,
1263 if(!IS_DEAD(it) && it.takedamage && !IS_INDEPENDENT_PLAYER(it))
1264 if(boxesoverlap(e.absmin, e.absmax, it.absmin, it.absmax))
1266 t = autocvar_g_balance_firetransfer_time * (e.fire_endtime - time);
1267 d = autocvar_g_balance_firetransfer_damage * e.fire_damagepersec * t;
1268 Fire_AddDamage(it, o, d, t, DEATH_FIRE.m_id);
1274 void Fire_ApplyEffect(entity e)
1276 if(Fire_IsBurning(e))
1277 e.effects |= EF_FLAME;
1279 e.effects &= ~EF_FLAME;
1282 void fireburner_think(entity this)
1284 // for players, this is done in the regular loop
1285 if(wasfreed(this.owner))
1290 Fire_ApplyEffect(this.owner);
1291 if(!Fire_IsBurning(this.owner))
1293 this.owner.fire_burner = NULL;
1297 Fire_ApplyDamage(this.owner);
1298 this.nextthink = time;