3 #include <common/effects/all.qh>
6 #include <server/client.qh>
7 #include <server/gamelog.qh>
8 #include <server/items/items.qh>
9 #include <server/mutators/_mod.qh>
10 #include <server/main.qh>
11 #include "teamplay.qh"
13 #include "spawnpoints.qh"
14 #include "../common/state.qh"
15 #include "../common/physics/player.qh"
16 #include "resources.qh"
17 #include "../common/vehicles/all.qh"
18 #include "../common/items/_mod.qh"
19 #include "../common/mutators/mutator/waypoints/waypointsprites.qh"
20 #include "../common/mutators/mutator/instagib/sv_instagib.qh"
21 #include "../common/mutators/mutator/buffs/buffs.qh"
22 #include "weapons/accuracy.qh"
23 #include "weapons/csqcprojectile.qh"
24 #include "weapons/selection.qh"
25 #include "../common/constants.qh"
26 #include "../common/deathtypes/all.qh"
27 #include <common/mapobjects/defs.qh>
28 #include <common/mapobjects/triggers.qh>
29 #include "../common/notifications/all.qh"
30 #include "../common/physics/movetypes/movetypes.qh"
31 #include "../common/playerstats.qh"
32 #include "../common/teams.qh"
33 #include "../common/util.qh"
34 #include <common/gamemodes/_mod.qh>
35 #include <common/gamemodes/rules.qh>
36 #include <common/weapons/_all.qh>
37 #include "../lib/csqcmodel/sv_model.qh"
38 #include "../lib/warpzone/common.qh"
40 void UpdateFrags(entity player, int f)
42 GameRules_scoring_add_team(player, SCORE, f);
45 void GiveFrags(entity attacker, entity targ, float f, int deathtype, .entity weaponentity)
47 // TODO route through PlayerScores instead
48 if(game_stopped) return;
55 GameRules_scoring_add(attacker, SUICIDES, 1);
60 GameRules_scoring_add(attacker, TEAMKILLS, 1);
66 GameRules_scoring_add(attacker, KILLS, 1);
67 if(!warmup_stage && targ.playerid)
68 PlayerStats_GameReport_Event_Player(attacker, sprintf("kills-%d", targ.playerid), 1);
71 GameRules_scoring_add(targ, DEATHS, 1);
73 // FIXME fix the mess this is (we have REAL points now!)
74 if(MUTATOR_CALLHOOK(GiveFragsForKill, attacker, targ, f, deathtype, attacker.(weaponentity)))
77 attacker.totalfrags += f;
80 UpdateFrags(attacker, f);
83 string AppendItemcodes(string s, entity player)
85 for(int slot = 0; slot < MAX_WEAPONSLOTS; ++slot)
87 .entity weaponentity = weaponentities[slot];
88 int w = player.(weaponentity).m_weapon.m_id;
90 w = player.(weaponentity).cnt; // previous weapon
91 if(w != 0 || slot == 0)
92 s = strcat(s, ftos(w));
94 if(time < STAT(STRENGTH_FINISHED, player))
96 if(time < STAT(INVINCIBLE_FINISHED, player))
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(
175 string s1, string s2, string s3,
178 Weapon death_weapon = DEATH_WEAPONOF(deathtype);
179 if (death_weapon == WEP_Null)
182 w_deathtype = deathtype;
183 Notification death_message = ((murder) ? death_weapon.wr_killmessage(death_weapon) : death_weapon.wr_suicidemessage(death_weapon));
188 Send_Notification_WOCOVA(
196 // send the info part to everyone
197 Send_Notification_WOCOVA(
201 death_message.nent_msginfo,
206 // z411 special medals
208 switch(death_message) {
209 case WEAPON_SHOTGUN_MURDER_SLAP:
210 if(!cvar("g_melee_only")) { // don't spam humiliation if we're in melee_only mode
211 Give_Medal(attacker, HUMILIATION);
214 case WEAPON_ELECTRO_MURDER_COMBO:
215 Give_Medal(attacker, ELECTROBITCH);
223 "Obituary_WeaponDeath(): ^1Deathtype ^7(%d)^1 has no notification for weapon %s!\n",
232 bool frag_centermessage_override(entity attacker, entity targ, int deathtype, int kill_count_to_attacker, int kill_count_to_target, string attacker_name)
234 if(deathtype == DEATH_FIRE.m_id)
236 Send_Notification(NOTIF_ONE, attacker, MSG_CHOICE, CHOICE_FRAG_FIRE, targ.netname, kill_count_to_attacker, (IS_BOT_CLIENT(targ) ? -1 : CS(targ).ping));
237 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));
241 return MUTATOR_CALLHOOK(FragCenterMessage, attacker, targ, deathtype, kill_count_to_attacker, kill_count_to_target);
244 void Obituary(entity attacker, entity inflictor, entity targ, int deathtype, .entity weaponentity)
247 if (!IS_PLAYER(targ)) { backtrace("Obituary called on non-player?!\n"); return; }
250 float notif_firstblood = false;
251 float kill_count_to_attacker, kill_count_to_target;
252 bool notif_anonymous = false;
253 string attacker_name = attacker.netname;
255 // Set final information for the death
256 targ.death_origin = targ.origin;
257 string deathlocation = (autocvar_notification_server_allows_location ? NearestLocation(targ.death_origin) : "");
259 // Abort now if a mutator requests it
260 if (MUTATOR_CALLHOOK(ClientObituary, inflictor, attacker, targ, deathtype, attacker.(weaponentity))) { CS(targ).killcount = 0; return; }
261 notif_anonymous = M_ARGV(5, bool);
264 attacker_name = "Anonymous player";
266 #ifdef NOTIFICATIONS_DEBUG
269 "Obituary(%s, %s, %s, %s = %d);\n",
273 Deathtype_Name(deathtype),
284 if(DEATH_ISSPECIAL(deathtype))
286 if(deathtype == DEATH_TEAMCHANGE.m_id || deathtype == DEATH_AUTOTEAMCHANGE.m_id)
288 Obituary_SpecialDeath(targ, false, deathtype, targ.netname, deathlocation, "", targ.team, 0, 0);
292 switch(DEATH_ENT(deathtype))
294 case DEATH_MIRRORDAMAGE:
296 Obituary_SpecialDeath(targ, false, deathtype, targ.netname, deathlocation, "", CS(targ).killcount, 0, 0);
302 Obituary_SpecialDeath(targ, false, deathtype, targ.netname, deathlocation, "", CS(targ).killcount, 0, 0);
308 else if (!Obituary_WeaponDeath(targ, NULL, false, deathtype, targ.netname, deathlocation, "", CS(targ).killcount, 0))
310 backtrace("SUICIDE: what the hell happened here?\n");
313 LogDeath("suicide", deathtype, targ, targ);
314 Send_Notification(NOTIF_ONE, targ, MSG_ANNCE, ANNCE_SUICIDE);
315 if(deathtype != DEATH_AUTOTEAMCHANGE.m_id) // special case: don't negate frags if auto switched
316 GiveFrags(attacker, targ, -1, deathtype, weaponentity);
322 else if(IS_PLAYER(attacker))
324 if(SAME_TEAM(attacker, targ))
326 LogDeath("tk", deathtype, attacker, targ);
327 GiveFrags(attacker, targ, -1, deathtype, weaponentity);
329 CS(attacker).killcount = 0;
331 Send_Notification(NOTIF_ONE, attacker, MSG_CENTER, CENTER_DEATH_TEAMKILL_FRAG, targ.netname);
332 Send_Notification(NOTIF_ONE, targ, MSG_CENTER, CENTER_DEATH_TEAMKILL_FRAGGED, attacker_name);
333 Send_Notification(NOTIF_ALL, NULL, MSG_INFO, APP_TEAM_NUM(targ.team, INFO_DEATH_TEAMKILL), playername(targ, true), playername(attacker, true), deathlocation, CS(targ).killcount);
335 // In this case, the death message will ALWAYS be "foo was betrayed by bar"
336 // No need for specific death/weapon messages...
340 LogDeath("frag", deathtype, attacker, targ);
341 GiveFrags(attacker, targ, 1, deathtype, weaponentity);
343 CS(attacker).taunt_soundtime = time + 1;
344 CS(attacker).killcount = CS(attacker).killcount + 1;
346 attacker.killsound += 1;
348 // TODO: improve SPREE_ITEM and KILL_SPREE_LIST
349 // these 2 macros are spread over multiple files
350 #define SPREE_ITEM(counta,countb,center,normal,gentle) \
352 Give_Medal(attacker, KILLSTREAK_##countb); \
354 PlayerStats_GameReport_Event_Player(attacker, PLAYERSTATS_ACHIEVEMENT_KILL_SPREE_##counta, 1); \
357 switch(CS(attacker).killcount)
364 if(!warmup_stage && !checkrules_firstblood)
366 checkrules_firstblood = true;
367 notif_firstblood = true; // modify the current messages so that they too show firstblood information
368 Give_Medal(attacker, FIRSTBLOOD);
369 PlayerStats_GameReport_Event_Player(attacker, PLAYERSTATS_ACHIEVEMENT_FIRSTBLOOD, 1);
370 PlayerStats_GameReport_Event_Player(targ, PLAYERSTATS_ACHIEVEMENT_FIRSTVICTIM, 1);
372 // tell spree_inf and spree_cen that this is a first-blood and first-victim event
373 kill_count_to_attacker = -1;
374 kill_count_to_target = -2;
378 kill_count_to_attacker = CS(attacker).killcount;
379 kill_count_to_target = 0;
383 if(attacker.lastkill && attacker.lastkill > time - 2) {
384 Give_Medal(attacker, EXCELLENT);
386 attacker.lastkill = time;
396 kill_count_to_attacker,
397 (IS_BOT_CLIENT(targ) ? -1 : CS(targ).ping)
405 kill_count_to_target,
406 GetResource(attacker, RES_HEALTH),
407 GetResource(attacker, RES_ARMOR),
408 (IS_BOT_CLIENT(attacker) ? -1 : CS(attacker).ping)
411 else if(!frag_centermessage_override(attacker, targ, deathtype, kill_count_to_attacker, kill_count_to_target, attacker_name))
419 kill_count_to_attacker,
420 (IS_BOT_CLIENT(targ) ? -1 : CS(targ).ping)
428 kill_count_to_target,
429 GetResource(attacker, RES_HEALTH),
430 GetResource(attacker, RES_ARMOR),
431 (IS_BOT_CLIENT(attacker) ? -1 : CS(attacker).ping)
436 if(deathtype == DEATH_BUFF.m_id)
437 f3 = buff_FirstFromFlags(STAT(BUFFS, attacker)).m_id;
439 if (!Obituary_WeaponDeath(targ, attacker, true, deathtype, playername(targ, true), playername(attacker, true), deathlocation, CS(targ).killcount, kill_count_to_attacker))
440 Obituary_SpecialDeath(targ, true, deathtype, playername(targ, true), playername(attacker, true), deathlocation, CS(targ).killcount, kill_count_to_attacker, f3);
449 switch(DEATH_ENT(deathtype))
451 // For now, we're just forcing HURTTRIGGER to behave as "DEATH_VOID" and giving it no special options...
452 // Later on you will only be able to make custom messages using DEATH_CUSTOM,
453 // and there will be a REAL DEATH_VOID implementation which mappers will use.
454 case DEATH_HURTTRIGGER:
456 Obituary_SpecialDeath(targ, false, deathtype,
457 playername(targ, true),
468 Obituary_SpecialDeath(targ, false, deathtype,
469 playername(targ, true),
470 ((strstrofs(deathmessage, "%", 0) < 0) ? strcat("%s ", deathmessage) : deathmessage),
480 Obituary_SpecialDeath(targ, false, deathtype, playername(targ, true), deathlocation, "", CS(targ).killcount, 0, 0);
485 LogDeath("accident", deathtype, targ, targ);
486 Send_Notification(NOTIF_ONE, targ, MSG_ANNCE, ANNCE_ACCIDENT);
487 GiveFrags(targ, targ, -1, deathtype, weaponentity);
489 if(GameRules_scoring_add(targ, SCORE, 0) == -5)
491 Send_Notification(NOTIF_ONE, targ, MSG_ANNCE, ANNCE_ACHIEVEMENT_BOTLIKE);
494 PlayerStats_GameReport_Event_Player(attacker, PLAYERSTATS_ACHIEVEMENT_BOTLIKE, 1);
499 // reset target kill count
500 CS(targ).killcount = 0;
503 void Ice_Think(entity this)
505 if(!STAT(FROZEN, this.owner) || this.owner.iceblock != this)
510 vector ice_org = this.owner.origin - '0 0 16';
511 if (this.origin != ice_org)
512 setorigin(this, ice_org);
513 this.nextthink = time;
516 void Freeze(entity targ, float revivespeed, int frozen_type, bool show_waypoint)
518 if(!IS_PLAYER(targ) && !IS_MONSTER(targ)) // TODO: only specified entities can be freezed
521 if(STAT(FROZEN, targ))
524 float targ_maxhealth = ((IS_MONSTER(targ)) ? targ.max_health : start_health);
526 STAT(FROZEN, targ) = frozen_type;
527 STAT(REVIVE_PROGRESS, targ) = ((frozen_type == FROZEN_TEMP_DYING) ? 1 : 0);
528 SetResource(targ, RES_HEALTH, ((frozen_type == FROZEN_TEMP_DYING) ? targ_maxhealth : 1));
529 targ.revive_speed = revivespeed;
531 IL_REMOVE(g_bot_targets, targ);
532 targ.bot_attack = false;
533 targ.freeze_time = time;
535 entity ice = new(ice);
537 ice.scale = targ.scale;
538 // set_movetype(ice, MOVETYPE_FOLLOW) would rotate the ice model with the player
539 setthink(ice, Ice_Think);
540 ice.nextthink = time;
541 ice.frame = floor(random() * 21); // ice model has 20 different looking frames
542 setmodel(ice, MDL_ICE);
544 ice.colormod = Team_ColorRGB(targ.team);
545 ice.glowmod = ice.colormod;
547 targ.revival_time = 0;
551 RemoveGrapplingHooks(targ);
553 FOREACH_CLIENT(IS_PLAYER(it),
555 for(int slot = 0; slot < MAX_WEAPONSLOTS; ++slot)
557 .entity weaponentity = weaponentities[slot];
558 if(it.(weaponentity).hook.aiment == targ)
559 RemoveHook(it.(weaponentity).hook);
564 if(MUTATOR_CALLHOOK(Freeze, targ, revivespeed, frozen_type) || show_waypoint)
565 WaypointSprite_Spawn(WP_Frozen, 0, 0, targ, '0 0 64', NULL, targ.team, targ, waypointsprite_attached, true, RADARICON_WAYPOINT);
568 void Unfreeze(entity targ, bool reset_health)
570 if(!STAT(FROZEN, targ))
573 if (reset_health && STAT(FROZEN, targ) != FROZEN_TEMP_DYING)
574 SetResource(targ, RES_HEALTH, ((IS_PLAYER(targ)) ? start_health : targ.max_health));
576 targ.pauseregen_finished = time + autocvar_g_balance_pause_health_regen;
578 STAT(FROZEN, targ) = 0;
579 STAT(REVIVE_PROGRESS, targ) = 0;
580 targ.revival_time = time;
582 IL_PUSH(g_bot_targets, targ);
583 targ.bot_attack = true;
585 WaypointSprite_Kill(targ.waypointsprite_attached);
587 FOREACH_CLIENT(IS_PLAYER(it),
589 for(int slot = 0; slot < MAX_WEAPONSLOTS; ++slot)
591 .entity weaponentity = weaponentities[slot];
592 if(it.(weaponentity).hook.aiment == targ)
593 RemoveHook(it.(weaponentity).hook);
597 // remove the ice block
599 delete(targ.iceblock);
600 targ.iceblock = NULL;
602 MUTATOR_CALLHOOK(Unfreeze, targ);
605 void Damage(entity targ, entity inflictor, entity attacker, float damage, int deathtype, .entity weaponentity, vector hitloc, vector force)
607 float complainteamdamage = 0;
608 float mirrordamage = 0;
609 float mirrorforce = 0;
611 if (game_stopped || (IS_CLIENT(targ) && CS(targ).killcount == FRAGS_SPECTATOR))
614 entity attacker_save = attacker;
616 // special rule: gravity bombs and sound-based attacks do not affect team mates (other than for disconnecting the hook)
617 if(DEATH_ISWEAPON(deathtype, WEP_HOOK) || (deathtype & HITTYPE_SOUND))
619 if(IS_PLAYER(targ) && SAME_TEAM(targ, attacker))
625 if(deathtype == DEATH_KILL.m_id || deathtype == DEATH_TEAMCHANGE.m_id || deathtype == DEATH_AUTOTEAMCHANGE.m_id)
627 // exit the vehicle before killing (fixes a crash)
628 if(IS_PLAYER(targ) && targ.vehicle)
629 vehicles_exit(targ.vehicle, VHEF_RELEASE);
631 // These are ALWAYS lethal
632 // No damage modification here
633 // Instead, prepare the victim for his death...
634 SetResourceExplicit(targ, RES_ARMOR, 0);
635 targ.spawnshieldtime = 0;
636 SetResourceExplicit(targ, RES_HEALTH, 0.9); // this is < 1
637 targ.flags -= targ.flags & FL_GODMODE;
640 else if(deathtype == DEATH_MIRRORDAMAGE.m_id || deathtype == DEATH_NOAMMO.m_id)
646 // nullify damage if teamplay is on
647 if(deathtype != DEATH_TELEFRAG.m_id)
648 if(IS_PLAYER(attacker))
650 if(IS_PLAYER(targ) && targ != attacker && (IS_INDEPENDENT_PLAYER(attacker) || IS_INDEPENDENT_PLAYER(targ)))
655 else if(SAME_TEAM(attacker, targ))
657 if(autocvar_teamplay_mode == 1)
659 else if(attacker != targ)
661 if(autocvar_teamplay_mode == 2)
663 if(IS_PLAYER(targ) && !IS_DEAD(targ))
665 attacker.dmg_team = attacker.dmg_team + damage;
666 complainteamdamage = attacker.dmg_team - autocvar_g_teamdamage_threshold;
669 else if(autocvar_teamplay_mode == 3)
671 else if(autocvar_teamplay_mode == 4)
673 if(IS_PLAYER(targ) && !IS_DEAD(targ))
675 attacker.dmg_team = attacker.dmg_team + damage;
676 complainteamdamage = attacker.dmg_team - autocvar_g_teamdamage_threshold;
677 if(complainteamdamage > 0)
678 mirrordamage = autocvar_g_mirrordamage * complainteamdamage;
679 mirrorforce = autocvar_g_mirrordamage * vlen(force);
680 damage = autocvar_g_friendlyfire * damage;
681 // mirrordamage will be used LATER
683 if(autocvar_g_mirrordamage_virtual)
685 vector v = healtharmor_applydamage(GetResource(attacker, RES_ARMOR), autocvar_g_balance_armor_blockpercent, deathtype, mirrordamage);
686 attacker.dmg_take += v.x;
687 attacker.dmg_save += v.y;
688 attacker.dmg_inflictor = inflictor;
693 if(autocvar_g_friendlyfire_virtual)
695 vector v = healtharmor_applydamage(GetResource(targ, RES_ARMOR), autocvar_g_balance_armor_blockpercent, deathtype, damage);
696 targ.dmg_take += v.x;
697 targ.dmg_save += v.y;
698 targ.dmg_inflictor = inflictor;
700 if(!autocvar_g_friendlyfire_virtual_force)
704 else if(!targ.canteamdamage)
711 if (!DEATH_ISSPECIAL(deathtype))
713 damage *= g_weapondamagefactor;
714 mirrordamage *= g_weapondamagefactor;
715 complainteamdamage *= g_weapondamagefactor;
716 force = force * g_weaponforcefactor;
717 mirrorforce *= g_weaponforcefactor;
720 // should this be changed at all? If so, in what way?
721 MUTATOR_CALLHOOK(Damage_Calculate, inflictor, attacker, targ, deathtype, damage, mirrordamage, force, attacker.(weaponentity));
722 damage = M_ARGV(4, float);
723 mirrordamage = M_ARGV(5, float);
724 force = M_ARGV(6, vector);
726 if(IS_PLAYER(targ) && damage > 0 && attacker)
728 for(int slot = 0; slot < MAX_WEAPONSLOTS; ++slot)
730 .entity went = weaponentities[slot];
731 if(targ.(went).hook && targ.(went).hook.aiment == attacker)
732 RemoveHook(targ.(went).hook);
736 if(STAT(FROZEN, targ) && !ITEM_DAMAGE_NEEDKILL(deathtype)
737 && deathtype != DEATH_TEAMCHANGE.m_id && deathtype != DEATH_AUTOTEAMCHANGE.m_id)
739 if(autocvar_g_frozen_revive_falldamage > 0 && deathtype == DEATH_FALL.m_id && damage >= autocvar_g_frozen_revive_falldamage)
741 Unfreeze(targ, false);
742 SetResource(targ, RES_HEALTH, autocvar_g_frozen_revive_falldamage_health);
743 Send_Effect(EFFECT_ICEORGLASS, targ.origin, '0 0 0', 3);
744 Send_Notification(NOTIF_ALL, NULL, MSG_INFO, INFO_FREEZETAG_REVIVED_FALL, targ.netname);
745 Send_Notification(NOTIF_ONE, targ, MSG_CENTER, CENTER_FREEZETAG_REVIVE_SELF);
749 force *= autocvar_g_frozen_force;
752 if(IS_PLAYER(targ) && STAT(FROZEN, targ)
753 && ITEM_DAMAGE_NEEDKILL(deathtype) && !autocvar_g_frozen_damage_trigger)
755 Send_Effect(EFFECT_TELEPORT, targ.origin, '0 0 0', 1);
757 entity spot = SelectSpawnPoint(targ, false);
761 targ.deadflag = DEAD_NO;
763 targ.angles = spot.angles;
766 targ.effects |= EF_TELEPORT_BIT;
768 targ.angles_z = 0; // never spawn tilted even if the spot says to
769 targ.fixangle = true; // turn this way immediately
770 targ.velocity = '0 0 0';
771 targ.avelocity = '0 0 0';
772 targ.punchangle = '0 0 0';
773 targ.punchvector = '0 0 0';
774 targ.oldvelocity = targ.velocity;
776 targ.spawnorigin = spot.origin;
777 setorigin(targ, spot.origin + '0 0 1' * (1 - targ.mins.z - 24));
778 // don't reset back to last position, even if new position is stuck in solid
779 targ.oldorigin = targ.origin;
781 Send_Effect(EFFECT_TELEPORT, targ.origin, '0 0 0', 1);
785 if(!MUTATOR_IS_ENABLED(mutator_instagib))
787 // apply strength multiplier
788 if (attacker.items & ITEM_Strength.m_itemid)
792 damage = damage * autocvar_g_balance_powerup_strength_selfdamage;
793 force = force * autocvar_g_balance_powerup_strength_selfforce;
797 damage = damage * autocvar_g_balance_powerup_strength_damage;
798 force = force * autocvar_g_balance_powerup_strength_force;
802 // apply invincibility multiplier
803 if (targ.items & ITEM_Shield.m_itemid)
805 damage = damage * autocvar_g_balance_powerup_invincible_takedamage;
806 if (targ != attacker)
808 force = force * autocvar_g_balance_powerup_invincible_takeforce;
813 if (targ == attacker)
814 damage = damage * autocvar_g_balance_selfdamagepercent; // Partial damage if the attacker hits himself
819 if(deathtype != DEATH_BUFF.m_id)
820 if(targ.takedamage == DAMAGE_AIM)
824 if(IS_VEHICLE(targ) && targ.owner)
829 if(IS_PLAYER(victim) || (IS_TURRET(victim) && victim.active == ACTIVE_ACTIVE) || IS_MONSTER(victim) || MUTATOR_CALLHOOK(PlayHitsound, victim, attacker))
831 if(DIFF_TEAM(victim, attacker) && !STAT(FROZEN, victim))
835 if(deathtype != DEATH_FIRE.m_id)
837 if(PHYS_INPUT_BUTTON_CHAT(victim))
838 attacker.typehitsound += 1;
840 attacker.damage_dealt += damage;
843 damage_goodhits += 1;
844 damage_gooddamage += damage;
846 if (!DEATH_ISSPECIAL(deathtype))
848 if(IS_PLAYER(targ)) // don't do this for vehicles
854 else if(IS_PLAYER(attacker))
856 // if enemy gets frozen in this frame and receives other damage don't
857 // play the typehitsound e.g. when hit by multiple bullets of the shotgun
858 if (deathtype != DEATH_FIRE.m_id && (!STAT(FROZEN, victim) || time > victim.freeze_time))
860 attacker.typehitsound += 1;
862 if(complainteamdamage > 0)
863 if(time > CS(attacker).teamkill_complain)
865 CS(attacker).teamkill_complain = time + 5;
866 CS(attacker).teamkill_soundtime = time + 0.4;
867 CS(attacker).teamkill_soundsource = targ;
875 if (targ.damageforcescale)
877 if (!IS_PLAYER(targ) || time >= targ.spawnshieldtime || targ == attacker)
879 vector farce = damage_explosion_calcpush(targ.damageforcescale * force, targ.velocity, autocvar_g_balance_damagepush_speedfactor);
880 if(targ.move_movetype == MOVETYPE_PHYSICS)
882 entity farcent = new(farce);
883 farcent.enemy = targ;
884 farcent.movedir = farce * 10;
886 farcent.movedir = farcent.movedir * targ.mass;
887 farcent.origin = hitloc;
888 farcent.forcetype = FORCETYPE_FORCEATPOS;
889 farcent.nextthink = time + 0.1;
890 setthink(farcent, SUB_Remove);
892 else if(targ.move_movetype != MOVETYPE_NOCLIP)
894 targ.velocity = targ.velocity + farce;
896 UNSET_ONGROUND(targ);
897 UpdateCSQCProjectile(targ);
900 if (damage != 0 || (targ.damageforcescale && force))
901 if (targ.event_damage)
902 targ.event_damage (targ, inflictor, attacker, damage, deathtype, weaponentity, hitloc, force);
904 // apply mirror damage if any
905 if(!autocvar_g_mirrordamage_onlyweapons || DEATH_WEAPONOF(deathtype) != WEP_Null)
906 if(mirrordamage > 0 || mirrorforce > 0)
908 attacker = attacker_save;
910 force = normalize(attacker.origin + attacker.view_ofs - hitloc) * mirrorforce;
911 Damage(attacker, inflictor, attacker, mirrordamage, DEATH_MIRRORDAMAGE.m_id, weaponentity, attacker.origin, force);
915 float RadiusDamageForSource (entity inflictor, vector inflictororigin, vector inflictorvelocity, entity attacker, float coredamage, float edgedamage, float rad, entity cantbe, entity mustbe,
916 float inflictorselfdamage, float forceintensity, float forcezscale, int deathtype, .entity weaponentity, entity directhitentity)
917 // Returns total damage applies to creatures
921 float total_damage_to_creatures;
926 float stat_damagedone;
928 if(RadiusDamage_running)
930 backtrace("RadiusDamage called recursively! Expect stuff to go HORRIBLY wrong.");
934 RadiusDamage_running = 1;
936 tfloordmg = autocvar_g_throughfloor_damage;
937 tfloorforce = autocvar_g_throughfloor_force;
939 total_damage_to_creatures = 0;
941 if(deathtype != (WEP_HOOK.m_id | HITTYPE_SECONDARY | HITTYPE_BOUNCE)) // only send gravity bomb damage once
942 if(!(deathtype & HITTYPE_SOUND)) // do not send radial sound damage (bandwidth hog)
944 force = inflictorvelocity;
948 force = normalize(force);
949 if(forceintensity >= 0)
950 Damage_DamageInfo(inflictororigin, coredamage, edgedamage, rad, forceintensity * force, deathtype, 0, attacker);
952 Damage_DamageInfo(inflictororigin, coredamage, edgedamage, -rad, (-forceintensity) * force, deathtype, 0, attacker);
957 targ = WarpZone_FindRadius (inflictororigin, rad + MAX_DAMAGEEXTRARADIUS, false);
961 if ((targ != inflictor) || inflictorselfdamage)
962 if (((cantbe != targ) && !mustbe) || (mustbe == targ))
969 // LordHavoc: measure distance to nearest point on target (not origin)
970 // (this guarentees 100% damage on a touch impact)
971 nearest = targ.WarpZone_findradius_nearest;
972 diff = targ.WarpZone_findradius_dist;
973 // round up a little on the damage to ensure full damage on impacts
974 // and turn the distance into a fraction of the radius
975 power = 1 - ((vlen (diff) - bound(MIN_DAMAGEEXTRARADIUS, targ.damageextraradius, MAX_DAMAGEEXTRARADIUS)) / rad);
977 //bprint(ftos(power));
978 //if (targ == attacker)
979 // print(ftos(power), "\n");
985 finaldmg = coredamage * power + edgedamage * (1 - power);
991 vector myblastorigin;
994 myblastorigin = WarpZone_TransformOrigin(targ, inflictororigin);
996 // if it's a player, use the view origin as reference
997 center = CENTER_OR_VIEWOFS(targ);
999 force = normalize(center - myblastorigin);
1000 force = force * (finaldmg / coredamage) * forceintensity;
1003 // apply special scaling along the z axis if set
1004 // NOTE: 0 value is not allowed for compatibility, in the case of weapon cvars not being set
1006 force.z *= forcezscale;
1008 if(targ != directhitentity)
1013 float mininv_f, mininv_d;
1015 // test line of sight to multiple positions on box,
1016 // and do damage if any of them hit
1019 // we know: max stddev of hitratio = 1 / (2 * sqrt(n))
1020 // so for a given max stddev:
1021 // n = (1 / (2 * max stddev of hitratio))^2
1023 mininv_d = (finaldmg * (1-tfloordmg)) / autocvar_g_throughfloor_damage_max_stddev;
1024 mininv_f = (vlen(force) * (1-tfloorforce)) / autocvar_g_throughfloor_force_max_stddev;
1026 if(autocvar_g_throughfloor_debug)
1027 LOG_INFOF("THROUGHFLOOR: D=%f F=%f max(dD)=1/%f max(dF)=1/%f", finaldmg, vlen(force), mininv_d, mininv_f);
1030 total = 0.25 * (max(mininv_f, mininv_d) ** 2);
1032 if(autocvar_g_throughfloor_debug)
1033 LOG_INFOF(" steps=%f", total);
1036 if (IS_PLAYER(targ))
1037 total = ceil(bound(autocvar_g_throughfloor_min_steps_player, total, autocvar_g_throughfloor_max_steps_player));
1039 total = ceil(bound(autocvar_g_throughfloor_min_steps_other, total, autocvar_g_throughfloor_max_steps_other));
1041 if(autocvar_g_throughfloor_debug)
1042 LOG_INFOF(" steps=%f dD=%f dF=%f", total, finaldmg * (1-tfloordmg) / (2 * sqrt(total)), vlen(force) * (1-tfloorforce) / (2 * sqrt(total)));
1044 for(c = 0; c < total; ++c)
1046 //traceline(targ.WarpZone_findradius_findorigin, nearest, MOVE_NOMONSTERS, inflictor);
1047 WarpZone_TraceLine(inflictororigin, WarpZone_UnTransformOrigin(targ, nearest), MOVE_NOMONSTERS, inflictor);
1048 if (trace_fraction == 1 || trace_ent == targ)
1052 hitloc = hitloc + nearest;
1056 nearest.x = targ.origin.x + targ.mins.x + random() * targ.size.x;
1057 nearest.y = targ.origin.y + targ.mins.y + random() * targ.size.y;
1058 nearest.z = targ.origin.z + targ.mins.z + random() * targ.size.z;
1061 nearest = hitloc * (1 / max(1, hits));
1062 hitratio = (hits / total);
1063 a = bound(0, tfloordmg + (1-tfloordmg) * hitratio, 1);
1064 finaldmg = finaldmg * a;
1065 a = bound(0, tfloorforce + (1-tfloorforce) * hitratio, 1);
1068 if(autocvar_g_throughfloor_debug)
1069 LOG_INFOF(" D=%f F=%f", finaldmg, vlen(force));
1072 //if (targ == attacker)
1074 // print("hits ", ftos(hits), " / ", ftos(total));
1075 // print(" finaldmg ", ftos(finaldmg), " force ", vtos(force));
1076 // print(" (", ftos(a), ")\n");
1078 if(finaldmg || force)
1082 total_damage_to_creatures += finaldmg;
1084 if(accuracy_isgooddamage(attacker, targ))
1085 stat_damagedone += finaldmg;
1088 if(targ == directhitentity || DEATH_ISSPECIAL(deathtype))
1089 Damage(targ, inflictor, attacker, finaldmg, deathtype, weaponentity, nearest, force);
1091 Damage(targ, inflictor, attacker, finaldmg, deathtype | HITTYPE_SPLASH, weaponentity, nearest, force);
1099 RadiusDamage_running = 0;
1101 if(!DEATH_ISSPECIAL(deathtype))
1102 accuracy_add(attacker, DEATH_WEAPONOF(deathtype), 0, min(coredamage, stat_damagedone));
1104 return total_damage_to_creatures;
1107 float RadiusDamage(entity inflictor, entity attacker, float coredamage, float edgedamage, float rad, entity cantbe, entity mustbe, float forceintensity, int deathtype, .entity weaponentity, entity directhitentity)
1109 return RadiusDamageForSource(inflictor, (inflictor.origin + (inflictor.mins + inflictor.maxs) * 0.5), inflictor.velocity, attacker, coredamage, edgedamage, rad,
1110 cantbe, mustbe, false, forceintensity, 1, deathtype, weaponentity, directhitentity);
1113 bool Heal(entity targ, entity inflictor, float amount, float limit)
1115 if(game_stopped || (IS_CLIENT(targ) && CS(targ).killcount == FRAGS_SPECTATOR) || STAT(FROZEN, targ) || IS_DEAD(targ))
1118 bool healed = false;
1120 healed = targ.event_heal(targ, inflictor, amount, limit);
1121 // TODO: additional handling? what if the healing kills them? should this abort if healing would do so etc
1122 // TODO: healing fx!
1123 // TODO: armor healing?
1127 float Fire_IsBurning(entity e)
1129 return (time < e.fire_endtime);
1132 float Fire_AddDamage(entity e, entity o, float d, float t, float dt)
1135 float maxtime, mintime, maxdamage, mindamage, maxdps, mindps, totaldamage, totaltime;
1146 // print("adding a fire burner to ", e.classname, "\n");
1147 e.fire_burner = new(fireburner);
1148 setthink(e.fire_burner, fireburner_think);
1149 e.fire_burner.nextthink = time;
1150 e.fire_burner.owner = e;
1156 if(Fire_IsBurning(e))
1158 mintime = e.fire_endtime - time;
1159 maxtime = max(mintime, t);
1161 mindps = e.fire_damagepersec;
1162 maxdps = max(mindps, dps);
1164 if(maxtime > mintime || maxdps > mindps)
1168 // damage we have right now
1169 mindamage = mindps * mintime;
1171 // damage we want to get
1172 maxdamage = mindamage + d;
1174 // but we can't exceed maxtime * maxdps!
1175 totaldamage = min(maxdamage, maxtime * maxdps);
1179 // totaldamage = min(mindamage + d, maxtime * maxdps)
1181 // totaldamage <= maxtime * maxdps
1182 // ==> totaldamage / maxdps <= maxtime.
1184 // totaldamage / mindps = min(mindamage / mindps + d, maxtime * maxdps / mindps)
1185 // >= min(mintime, maxtime)
1186 // ==> totaldamage / maxdps >= mintime.
1189 // how long do we damage then?
1190 // at least as long as before
1191 // but, never exceed maxdps
1192 totaltime = max(mintime, totaldamage / maxdps); // always <= maxtime due to lemma
1196 // at most as long as maximum allowed
1197 // but, never below mindps
1198 totaltime = min(maxtime, totaldamage / mindps); // always >= mintime due to lemma
1200 // assuming t > mintime, dps > mindps:
1201 // we get d = t * dps = maxtime * maxdps
1202 // totaldamage = min(maxdamage, maxtime * maxdps) = min(... + d, maxtime * maxdps) = maxtime * maxdps
1203 // totaldamage / maxdps = maxtime
1204 // totaldamage / mindps > totaldamage / maxdps = maxtime
1206 // a) totaltime = max(mintime, maxtime) = maxtime
1207 // b) totaltime = min(maxtime, totaldamage / maxdps) = maxtime
1209 // assuming t <= mintime:
1210 // we get maxtime = mintime
1211 // a) totaltime = max(mintime, ...) >= mintime, also totaltime <= maxtime by the lemma, therefore totaltime = mintime = maxtime
1212 // b) totaltime = min(maxtime, ...) <= maxtime, also totaltime >= mintime by the lemma, therefore totaltime = mintime = maxtime
1214 // assuming dps <= mindps:
1215 // we get mindps = maxdps.
1216 // With this, the lemma says that mintime <= totaldamage / mindps = totaldamage / maxdps <= maxtime.
1217 // a) totaltime = max(mintime, totaldamage / maxdps) = totaldamage / maxdps
1218 // b) totaltime = min(maxtime, totaldamage / mindps) = totaldamage / maxdps
1220 e.fire_damagepersec = totaldamage / totaltime;
1221 e.fire_endtime = time + totaltime;
1222 if(totaldamage > 1.2 * mindamage)
1224 e.fire_deathtype = dt;
1225 if(e.fire_owner != o)
1228 e.fire_hitsound = false;
1231 if(accuracy_isgooddamage(o, e))
1232 accuracy_add(o, DEATH_WEAPONOF(dt), 0, max(0, totaldamage - mindamage));
1233 return max(0, totaldamage - mindamage); // can never be negative, but to make sure
1240 e.fire_damagepersec = dps;
1241 e.fire_endtime = time + t;
1242 e.fire_deathtype = dt;
1244 e.fire_hitsound = false;
1245 if(accuracy_isgooddamage(o, e))
1246 accuracy_add(o, DEATH_WEAPONOF(dt), 0, d);
1251 void Fire_ApplyDamage(entity e)
1256 if (!Fire_IsBurning(e))
1259 for(t = 0, o = e.owner; o.owner && t < 16; o = o.owner, ++t);
1260 if(IS_NOT_A_CLIENT(o))
1263 // water and slime stop fire
1265 if(e.watertype != CONTENT_LAVA)
1272 t = min(frametime, e.fire_endtime - time);
1273 d = e.fire_damagepersec * t;
1275 hi = e.fire_owner.damage_dealt;
1276 ty = e.fire_owner.typehitsound;
1277 Damage(e, e, e.fire_owner, d, e.fire_deathtype, DMG_NOWEP, e.origin, '0 0 0');
1278 if(e.fire_hitsound && e.fire_owner)
1280 e.fire_owner.damage_dealt = hi;
1281 e.fire_owner.typehitsound = ty;
1283 e.fire_hitsound = true;
1285 if(!IS_INDEPENDENT_PLAYER(e) && !STAT(FROZEN, e))
1287 IL_EACH(g_damagedbycontents, it.damagedbycontents && it != e,
1289 if(!IS_DEAD(it) && it.takedamage && !IS_INDEPENDENT_PLAYER(it))
1290 if(boxesoverlap(e.absmin, e.absmax, it.absmin, it.absmax))
1292 t = autocvar_g_balance_firetransfer_time * (e.fire_endtime - time);
1293 d = autocvar_g_balance_firetransfer_damage * e.fire_damagepersec * t;
1294 Fire_AddDamage(it, o, d, t, DEATH_FIRE.m_id);
1300 void Fire_ApplyEffect(entity e)
1302 if(Fire_IsBurning(e))
1303 e.effects |= EF_FLAME;
1305 e.effects &= ~EF_FLAME;
1308 void fireburner_think(entity this)
1310 // for players, this is done in the regular loop
1311 if(wasfreed(this.owner))
1316 Fire_ApplyEffect(this.owner);
1317 if(!Fire_IsBurning(this.owner))
1319 this.owner.fire_burner = NULL;
1323 Fire_ApplyDamage(this.owner);
1324 this.nextthink = time;