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/rules.qh>
31 #include <common/weapons/_all.qh>
32 #include "../lib/csqcmodel/sv_model.qh"
33 #include "../lib/warpzone/common.qh"
35 void UpdateFrags(entity player, int f)
37 GameRules_scoring_add_team(player, SCORE, f);
40 void GiveFrags(entity attacker, entity targ, float f, int deathtype, .entity weaponentity)
42 // TODO route through PlayerScores instead
43 if(game_stopped) return;
50 GameRules_scoring_add(attacker, SUICIDES, 1);
55 GameRules_scoring_add(attacker, TEAMKILLS, 1);
61 GameRules_scoring_add(attacker, KILLS, 1);
62 if(!warmup_stage && targ.playerid)
63 PlayerStats_GameReport_Event_Player(attacker, sprintf("kills-%d", targ.playerid), 1);
66 GameRules_scoring_add(targ, DEATHS, 1);
68 // FIXME fix the mess this is (we have REAL points now!)
69 if(MUTATOR_CALLHOOK(GiveFragsForKill, attacker, targ, f, deathtype, attacker.(weaponentity)))
72 attacker.totalfrags += f;
75 UpdateFrags(attacker, f);
78 string AppendItemcodes(string s, entity player)
80 for(int slot = 0; slot < MAX_WEAPONSLOTS; ++slot)
82 .entity weaponentity = weaponentities[slot];
83 int w = player.(weaponentity).m_weapon.m_id;
85 w = player.(weaponentity).cnt; // previous weapon
86 if(w != 0 || slot == 0)
87 s = strcat(s, ftos(w));
89 if(time < STAT(STRENGTH_FINISHED, player))
91 if(time < STAT(INVINCIBLE_FINISHED, player))
93 if(PHYS_INPUT_BUTTON_CHAT(player))
95 // TODO: include these codes as a flag on the item itself
96 MUTATOR_CALLHOOK(LogDeath_AppendItemCodes, player, s);
97 s = M_ARGV(1, string);
101 void LogDeath(string mode, int deathtype, entity killer, entity killed)
104 if(!autocvar_sv_eventlog)
106 s = strcat(":kill:", mode);
107 s = strcat(s, ":", ftos(killer.playerid));
108 s = strcat(s, ":", ftos(killed.playerid));
109 s = strcat(s, ":type=", Deathtype_Name(deathtype));
110 s = strcat(s, ":items=");
111 s = AppendItemcodes(s, killer);
114 s = strcat(s, ":victimitems=");
115 s = AppendItemcodes(s, killed);
120 void Obituary_SpecialDeath(
124 string s1, string s2, string s3,
125 float f1, float f2, float f3)
127 if(!DEATH_ISSPECIAL(deathtype))
129 backtrace("Obituary_SpecialDeath called without a special deathtype?\n");
133 entity deathent = REGISTRY_GET(Deathtypes, deathtype - DT_FIRST);
136 backtrace("Obituary_SpecialDeath: Could not find deathtype entity!\n");
140 if(g_cts && deathtype == DEATH_KILL.m_id)
141 return; // TODO: somehow put this in CTS gamemode file!
143 Notification death_message = (murder) ? deathent.death_msgmurder : deathent.death_msgself;
146 Send_Notification_WOCOVA(
154 Send_Notification_WOCOVA(
158 death_message.nent_msginfo,
165 float Obituary_WeaponDeath(
169 string s1, string s2, string s3,
172 Weapon death_weapon = DEATH_WEAPONOF(deathtype);
173 if (death_weapon == WEP_Null)
176 w_deathtype = deathtype;
177 Notification death_message = ((murder) ? death_weapon.wr_killmessage(death_weapon) : death_weapon.wr_suicidemessage(death_weapon));
182 Send_Notification_WOCOVA(
190 // send the info part to everyone
191 Send_Notification_WOCOVA(
195 death_message.nent_msginfo,
203 "Obituary_WeaponDeath(): ^1Deathtype ^7(%d)^1 has no notification for weapon %s!\n",
212 bool frag_centermessage_override(entity attacker, entity targ, int deathtype, int kill_count_to_attacker, int kill_count_to_target, string attacker_name)
214 if(deathtype == DEATH_FIRE.m_id)
216 Send_Notification(NOTIF_ONE, attacker, MSG_CHOICE, CHOICE_FRAG_FIRE, targ.netname, kill_count_to_attacker, (IS_BOT_CLIENT(targ) ? -1 : CS(targ).ping));
217 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));
221 return MUTATOR_CALLHOOK(FragCenterMessage, attacker, targ, deathtype, kill_count_to_attacker, kill_count_to_target);
224 void Obituary(entity attacker, entity inflictor, entity targ, int deathtype, .entity weaponentity)
227 if (!IS_PLAYER(targ)) { backtrace("Obituary called on non-player?!\n"); return; }
230 float notif_firstblood = false;
231 float kill_count_to_attacker, kill_count_to_target;
232 bool notif_anonymous = false;
233 string attacker_name = attacker.netname;
235 // Set final information for the death
236 targ.death_origin = targ.origin;
237 string deathlocation = (autocvar_notification_server_allows_location ? NearestLocation(targ.death_origin) : "");
239 // Abort now if a mutator requests it
240 if (MUTATOR_CALLHOOK(ClientObituary, inflictor, attacker, targ, deathtype, attacker.(weaponentity))) { CS(targ).killcount = 0; return; }
241 notif_anonymous = M_ARGV(5, bool);
244 attacker_name = "Anonymous player";
246 #ifdef NOTIFICATIONS_DEBUG
249 "Obituary(%s, %s, %s, %s = %d);\n",
253 Deathtype_Name(deathtype),
264 if(DEATH_ISSPECIAL(deathtype))
266 if(deathtype == DEATH_TEAMCHANGE.m_id || deathtype == DEATH_AUTOTEAMCHANGE.m_id)
268 Obituary_SpecialDeath(targ, false, deathtype, targ.netname, deathlocation, "", targ.team, 0, 0);
272 switch(DEATH_ENT(deathtype))
274 case DEATH_MIRRORDAMAGE:
276 Obituary_SpecialDeath(targ, false, deathtype, targ.netname, deathlocation, "", CS(targ).killcount, 0, 0);
282 Obituary_SpecialDeath(targ, false, deathtype, targ.netname, deathlocation, "", CS(targ).killcount, 0, 0);
288 else if (!Obituary_WeaponDeath(targ, false, deathtype, targ.netname, deathlocation, "", CS(targ).killcount, 0))
290 backtrace("SUICIDE: what the hell happened here?\n");
293 LogDeath("suicide", deathtype, targ, targ);
294 if(deathtype != DEATH_AUTOTEAMCHANGE.m_id) // special case: don't negate frags if auto switched
295 GiveFrags(attacker, targ, -1, deathtype, weaponentity);
301 else if(IS_PLAYER(attacker))
303 if(SAME_TEAM(attacker, targ))
305 LogDeath("tk", deathtype, attacker, targ);
306 GiveFrags(attacker, targ, -1, deathtype, weaponentity);
308 CS(attacker).killcount = 0;
310 Send_Notification(NOTIF_ONE, attacker, MSG_CENTER, CENTER_DEATH_TEAMKILL_FRAG, targ.netname);
311 Send_Notification(NOTIF_ONE, targ, MSG_CENTER, CENTER_DEATH_TEAMKILL_FRAGGED, attacker_name);
312 Send_Notification(NOTIF_ALL, NULL, MSG_INFO, APP_TEAM_NUM(targ.team, INFO_DEATH_TEAMKILL), targ.netname, attacker_name, deathlocation, CS(targ).killcount);
314 // In this case, the death message will ALWAYS be "foo was betrayed by bar"
315 // No need for specific death/weapon messages...
319 LogDeath("frag", deathtype, attacker, targ);
320 GiveFrags(attacker, targ, 1, deathtype, weaponentity);
322 CS(attacker).taunt_soundtime = time + 1;
323 CS(attacker).killcount = CS(attacker).killcount + 1;
325 attacker.killsound += 1;
327 // TODO: improve SPREE_ITEM and KILL_SPREE_LIST
328 // these 2 macros are spread over multiple files
329 #define SPREE_ITEM(counta,countb,center,normal,gentle) \
331 Send_Notification(NOTIF_ONE, attacker, MSG_ANNCE, ANNCE_KILLSTREAK_##countb); \
333 PlayerStats_GameReport_Event_Player(attacker, PLAYERSTATS_ACHIEVEMENT_KILL_SPREE_##counta, 1); \
336 switch(CS(attacker).killcount)
343 if(!warmup_stage && !checkrules_firstblood)
345 checkrules_firstblood = true;
346 notif_firstblood = true; // modify the current messages so that they too show firstblood information
347 PlayerStats_GameReport_Event_Player(attacker, PLAYERSTATS_ACHIEVEMENT_FIRSTBLOOD, 1);
348 PlayerStats_GameReport_Event_Player(targ, PLAYERSTATS_ACHIEVEMENT_FIRSTVICTIM, 1);
350 // tell spree_inf and spree_cen that this is a first-blood and first-victim event
351 kill_count_to_attacker = -1;
352 kill_count_to_target = -2;
356 kill_count_to_attacker = CS(attacker).killcount;
357 kill_count_to_target = 0;
368 kill_count_to_attacker,
369 (IS_BOT_CLIENT(targ) ? -1 : CS(targ).ping)
377 kill_count_to_target,
378 GetResource(attacker, RES_HEALTH),
379 GetResource(attacker, RES_ARMOR),
380 (IS_BOT_CLIENT(attacker) ? -1 : CS(attacker).ping)
383 else if(!frag_centermessage_override(attacker, targ, deathtype, kill_count_to_attacker, kill_count_to_target, attacker_name))
391 kill_count_to_attacker,
392 (IS_BOT_CLIENT(targ) ? -1 : CS(targ).ping)
400 kill_count_to_target,
401 GetResource(attacker, RES_HEALTH),
402 GetResource(attacker, RES_ARMOR),
403 (IS_BOT_CLIENT(attacker) ? -1 : CS(attacker).ping)
408 if(deathtype == DEATH_BUFF.m_id)
409 f3 = buff_FirstFromFlags(STAT(BUFFS, attacker)).m_id;
411 if (!Obituary_WeaponDeath(targ, true, deathtype, targ.netname, attacker_name, deathlocation, CS(targ).killcount, kill_count_to_attacker))
412 Obituary_SpecialDeath(targ, true, deathtype, targ.netname, attacker_name, deathlocation, CS(targ).killcount, kill_count_to_attacker, f3);
421 switch(DEATH_ENT(deathtype))
423 // For now, we're just forcing HURTTRIGGER to behave as "DEATH_VOID" and giving it no special options...
424 // Later on you will only be able to make custom messages using DEATH_CUSTOM,
425 // and there will be a REAL DEATH_VOID implementation which mappers will use.
426 case DEATH_HURTTRIGGER:
428 Obituary_SpecialDeath(targ, false, deathtype,
440 Obituary_SpecialDeath(targ, false, deathtype,
442 ((strstrofs(deathmessage, "%", 0) < 0) ? strcat("%s ", deathmessage) : deathmessage),
452 Obituary_SpecialDeath(targ, false, deathtype, targ.netname, deathlocation, "", CS(targ).killcount, 0, 0);
457 LogDeath("accident", deathtype, targ, targ);
458 GiveFrags(targ, targ, -1, deathtype, weaponentity);
460 if(GameRules_scoring_add(targ, SCORE, 0) == -5)
462 Send_Notification(NOTIF_ONE, targ, MSG_ANNCE, ANNCE_ACHIEVEMENT_BOTLIKE);
465 PlayerStats_GameReport_Event_Player(attacker, PLAYERSTATS_ACHIEVEMENT_BOTLIKE, 1);
470 // reset target kill count
471 CS(targ).killcount = 0;
474 void Ice_Think(entity this)
476 if(!STAT(FROZEN, this.owner) || this.owner.iceblock != this)
481 vector ice_org = this.owner.origin - '0 0 16';
482 if (this.origin != ice_org)
483 setorigin(this, ice_org);
484 this.nextthink = time;
487 void Freeze(entity targ, float revivespeed, int frozen_type, bool show_waypoint)
489 if(!IS_PLAYER(targ) && !IS_MONSTER(targ)) // TODO: only specified entities can be freezed
492 if(STAT(FROZEN, targ))
495 float targ_maxhealth = ((IS_MONSTER(targ)) ? targ.max_health : start_health);
497 STAT(FROZEN, targ) = frozen_type;
498 STAT(REVIVE_PROGRESS, targ) = ((frozen_type == FROZEN_TEMP_DYING) ? 1 : 0);
499 SetResource(targ, RES_HEALTH, ((frozen_type == FROZEN_TEMP_DYING) ? targ_maxhealth : 1));
500 targ.revive_speed = revivespeed;
502 IL_REMOVE(g_bot_targets, targ);
503 targ.bot_attack = false;
504 targ.freeze_time = time;
506 entity ice = new(ice);
508 ice.scale = targ.scale;
509 // set_movetype(ice, MOVETYPE_FOLLOW) would rotate the ice model with the player
510 setthink(ice, Ice_Think);
511 ice.nextthink = time;
512 ice.frame = floor(random() * 21); // ice model has 20 different looking frames
513 setmodel(ice, MDL_ICE);
515 ice.colormod = Team_ColorRGB(targ.team);
516 ice.glowmod = ice.colormod;
518 targ.revival_time = 0;
522 RemoveGrapplingHooks(targ);
524 FOREACH_CLIENT(IS_PLAYER(it),
526 for(int slot = 0; slot < MAX_WEAPONSLOTS; ++slot)
528 .entity weaponentity = weaponentities[slot];
529 if(it.(weaponentity).hook.aiment == targ)
530 RemoveHook(it.(weaponentity).hook);
535 if(MUTATOR_CALLHOOK(Freeze, targ, revivespeed, frozen_type) || show_waypoint)
536 WaypointSprite_Spawn(WP_Frozen, 0, 0, targ, '0 0 64', NULL, targ.team, targ, waypointsprite_attached, true, RADARICON_WAYPOINT);
539 void Unfreeze(entity targ, bool reset_health)
541 if(!STAT(FROZEN, targ))
544 if (reset_health && STAT(FROZEN, targ) != FROZEN_TEMP_DYING)
545 SetResource(targ, RES_HEALTH, ((IS_PLAYER(targ)) ? start_health : targ.max_health));
547 targ.pauseregen_finished = time + autocvar_g_balance_pause_health_regen;
549 STAT(FROZEN, targ) = 0;
550 STAT(REVIVE_PROGRESS, targ) = 0;
551 targ.revival_time = time;
553 IL_PUSH(g_bot_targets, targ);
554 targ.bot_attack = true;
556 WaypointSprite_Kill(targ.waypointsprite_attached);
558 FOREACH_CLIENT(IS_PLAYER(it),
560 for(int slot = 0; slot < MAX_WEAPONSLOTS; ++slot)
562 .entity weaponentity = weaponentities[slot];
563 if(it.(weaponentity).hook.aiment == targ)
564 RemoveHook(it.(weaponentity).hook);
568 // remove the ice block
570 delete(targ.iceblock);
571 targ.iceblock = NULL;
573 MUTATOR_CALLHOOK(Unfreeze, targ);
576 void Damage(entity targ, entity inflictor, entity attacker, float damage, int deathtype, .entity weaponentity, vector hitloc, vector force)
578 float complainteamdamage = 0;
579 float mirrordamage = 0;
580 float mirrorforce = 0;
582 if (game_stopped || (IS_CLIENT(targ) && CS(targ).killcount == FRAGS_SPECTATOR))
585 entity attacker_save = attacker;
587 // special rule: gravity bomb does not hit team mates (other than for disconnecting the hook)
588 if(DEATH_ISWEAPON(deathtype, WEP_HOOK) || DEATH_ISWEAPON(deathtype, WEP_TUBA))
590 if(IS_PLAYER(targ) && SAME_TEAM(targ, attacker))
596 if(deathtype == DEATH_KILL.m_id || deathtype == DEATH_TEAMCHANGE.m_id || deathtype == DEATH_AUTOTEAMCHANGE.m_id)
598 // exit the vehicle before killing (fixes a crash)
599 if(IS_PLAYER(targ) && targ.vehicle)
600 vehicles_exit(targ.vehicle, VHEF_RELEASE);
602 // These are ALWAYS lethal
603 // No damage modification here
604 // Instead, prepare the victim for his death...
605 SetResourceExplicit(targ, RES_ARMOR, 0);
606 targ.spawnshieldtime = 0;
607 SetResourceExplicit(targ, RES_HEALTH, 0.9); // this is < 1
608 targ.flags -= targ.flags & FL_GODMODE;
611 else if(deathtype == DEATH_MIRRORDAMAGE.m_id || deathtype == DEATH_NOAMMO.m_id)
617 // nullify damage if teamplay is on
618 if(deathtype != DEATH_TELEFRAG.m_id)
619 if(IS_PLAYER(attacker))
621 if(IS_PLAYER(targ) && targ != attacker && (IS_INDEPENDENT_PLAYER(attacker) || IS_INDEPENDENT_PLAYER(targ)))
626 else if(SAME_TEAM(attacker, targ))
628 if(autocvar_teamplay_mode == 1)
630 else if(attacker != targ)
632 if(autocvar_teamplay_mode == 2)
634 if(IS_PLAYER(targ) && !IS_DEAD(targ))
636 attacker.dmg_team = attacker.dmg_team + damage;
637 complainteamdamage = attacker.dmg_team - autocvar_g_teamdamage_threshold;
640 else if(autocvar_teamplay_mode == 3)
642 else if(autocvar_teamplay_mode == 4)
644 if(IS_PLAYER(targ) && !IS_DEAD(targ))
646 attacker.dmg_team = attacker.dmg_team + damage;
647 complainteamdamage = attacker.dmg_team - autocvar_g_teamdamage_threshold;
648 if(complainteamdamage > 0)
649 mirrordamage = autocvar_g_mirrordamage * complainteamdamage;
650 mirrorforce = autocvar_g_mirrordamage * vlen(force);
651 damage = autocvar_g_friendlyfire * damage;
652 // mirrordamage will be used LATER
654 if(autocvar_g_mirrordamage_virtual)
656 vector v = healtharmor_applydamage(GetResource(attacker, RES_ARMOR), autocvar_g_balance_armor_blockpercent, deathtype, mirrordamage);
657 attacker.dmg_take += v.x;
658 attacker.dmg_save += v.y;
659 attacker.dmg_inflictor = inflictor;
664 if(autocvar_g_friendlyfire_virtual)
666 vector v = healtharmor_applydamage(GetResource(targ, RES_ARMOR), autocvar_g_balance_armor_blockpercent, deathtype, damage);
667 targ.dmg_take += v.x;
668 targ.dmg_save += v.y;
669 targ.dmg_inflictor = inflictor;
671 if(!autocvar_g_friendlyfire_virtual_force)
675 else if(!targ.canteamdamage)
682 if (!DEATH_ISSPECIAL(deathtype))
684 damage *= g_weapondamagefactor;
685 mirrordamage *= g_weapondamagefactor;
686 complainteamdamage *= g_weapondamagefactor;
687 force = force * g_weaponforcefactor;
688 mirrorforce *= g_weaponforcefactor;
691 // should this be changed at all? If so, in what way?
692 MUTATOR_CALLHOOK(Damage_Calculate, inflictor, attacker, targ, deathtype, damage, mirrordamage, force, attacker.(weaponentity));
693 damage = M_ARGV(4, float);
694 mirrordamage = M_ARGV(5, float);
695 force = M_ARGV(6, vector);
697 if(IS_PLAYER(targ) && damage > 0 && attacker)
699 for(int slot = 0; slot < MAX_WEAPONSLOTS; ++slot)
701 .entity went = weaponentities[slot];
702 if(targ.(went).hook && targ.(went).hook.aiment == attacker)
703 RemoveHook(targ.(went).hook);
707 if(STAT(FROZEN, targ) && !ITEM_DAMAGE_NEEDKILL(deathtype)
708 && deathtype != DEATH_TEAMCHANGE.m_id && deathtype != DEATH_AUTOTEAMCHANGE.m_id)
710 if(autocvar_g_frozen_revive_falldamage > 0 && deathtype == DEATH_FALL.m_id && damage >= autocvar_g_frozen_revive_falldamage)
712 Unfreeze(targ, false);
713 SetResource(targ, RES_HEALTH, autocvar_g_frozen_revive_falldamage_health);
714 Send_Effect(EFFECT_ICEORGLASS, targ.origin, '0 0 0', 3);
715 Send_Notification(NOTIF_ALL, NULL, MSG_INFO, INFO_FREEZETAG_REVIVED_FALL, targ.netname);
716 Send_Notification(NOTIF_ONE, targ, MSG_CENTER, CENTER_FREEZETAG_REVIVE_SELF);
720 force *= autocvar_g_frozen_force;
723 if(IS_PLAYER(targ) && STAT(FROZEN, targ)
724 && ITEM_DAMAGE_NEEDKILL(deathtype) && !autocvar_g_frozen_damage_trigger)
726 Send_Effect(EFFECT_TELEPORT, targ.origin, '0 0 0', 1);
728 entity spot = SelectSpawnPoint(targ, false);
732 targ.deadflag = DEAD_NO;
734 targ.angles = spot.angles;
737 targ.effects |= EF_TELEPORT_BIT;
739 targ.angles_z = 0; // never spawn tilted even if the spot says to
740 targ.fixangle = true; // turn this way immediately
741 targ.velocity = '0 0 0';
742 targ.avelocity = '0 0 0';
743 targ.punchangle = '0 0 0';
744 targ.punchvector = '0 0 0';
745 targ.oldvelocity = targ.velocity;
747 targ.spawnorigin = spot.origin;
748 setorigin(targ, spot.origin + '0 0 1' * (1 - targ.mins.z - 24));
749 // don't reset back to last position, even if new position is stuck in solid
750 targ.oldorigin = targ.origin;
752 Send_Effect(EFFECT_TELEPORT, targ.origin, '0 0 0', 1);
756 if(!MUTATOR_IS_ENABLED(mutator_instagib))
758 // apply strength multiplier
759 if (attacker.items & ITEM_Strength.m_itemid)
763 damage = damage * autocvar_g_balance_powerup_strength_selfdamage;
764 force = force * autocvar_g_balance_powerup_strength_selfforce;
768 damage = damage * autocvar_g_balance_powerup_strength_damage;
769 force = force * autocvar_g_balance_powerup_strength_force;
773 // apply invincibility multiplier
774 if (targ.items & ITEM_Shield.m_itemid)
776 damage = damage * autocvar_g_balance_powerup_invincible_takedamage;
777 if (targ != attacker)
779 force = force * autocvar_g_balance_powerup_invincible_takeforce;
784 if (targ == attacker)
785 damage = damage * autocvar_g_balance_selfdamagepercent; // Partial damage if the attacker hits himself
790 if(deathtype != DEATH_BUFF.m_id)
791 if(targ.takedamage == DAMAGE_AIM)
795 if(IS_VEHICLE(targ) && targ.owner)
800 if(IS_PLAYER(victim) || (IS_TURRET(victim) && victim.active == ACTIVE_ACTIVE) || IS_MONSTER(victim) || MUTATOR_CALLHOOK(PlayHitsound, victim, attacker))
802 if(DIFF_TEAM(victim, attacker) && !STAT(FROZEN, victim))
806 if(deathtype != DEATH_FIRE.m_id)
808 if(PHYS_INPUT_BUTTON_CHAT(victim))
809 attacker.typehitsound += 1;
811 attacker.damage_dealt += damage;
814 damage_goodhits += 1;
815 damage_gooddamage += damage;
817 if (!DEATH_ISSPECIAL(deathtype))
819 if(IS_PLAYER(targ)) // don't do this for vehicles
825 else if(IS_PLAYER(attacker))
827 // if enemy gets frozen in this frame and receives other damage don't
828 // play the typehitsound e.g. when hit by multiple bullets of the shotgun
829 if (deathtype != DEATH_FIRE.m_id && (!STAT(FROZEN, victim) || time > victim.freeze_time))
831 attacker.typehitsound += 1;
833 if(complainteamdamage > 0)
834 if(time > CS(attacker).teamkill_complain)
836 CS(attacker).teamkill_complain = time + 5;
837 CS(attacker).teamkill_soundtime = time + 0.4;
838 CS(attacker).teamkill_soundsource = targ;
846 if (targ.damageforcescale)
848 if (!IS_PLAYER(targ) || time >= targ.spawnshieldtime || targ == attacker)
850 vector farce = damage_explosion_calcpush(targ.damageforcescale * force, targ.velocity, autocvar_g_balance_damagepush_speedfactor);
851 if(targ.move_movetype == MOVETYPE_PHYSICS)
853 entity farcent = new(farce);
854 farcent.enemy = targ;
855 farcent.movedir = farce * 10;
857 farcent.movedir = farcent.movedir * targ.mass;
858 farcent.origin = hitloc;
859 farcent.forcetype = FORCETYPE_FORCEATPOS;
860 farcent.nextthink = time + 0.1;
861 setthink(farcent, SUB_Remove);
865 targ.velocity = targ.velocity + farce;
867 UNSET_ONGROUND(targ);
868 UpdateCSQCProjectile(targ);
871 if (damage != 0 || (targ.damageforcescale && force))
872 if (targ.event_damage)
873 targ.event_damage (targ, inflictor, attacker, damage, deathtype, weaponentity, hitloc, force);
875 // apply mirror damage if any
876 if(!autocvar_g_mirrordamage_onlyweapons || DEATH_WEAPONOF(deathtype) != WEP_Null)
877 if(mirrordamage > 0 || mirrorforce > 0)
879 attacker = attacker_save;
881 force = normalize(attacker.origin + attacker.view_ofs - hitloc) * mirrorforce;
882 Damage(attacker, inflictor, attacker, mirrordamage, DEATH_MIRRORDAMAGE.m_id, weaponentity, attacker.origin, force);
886 float RadiusDamageForSource (entity inflictor, vector inflictororigin, vector inflictorvelocity, entity attacker, float coredamage, float edgedamage, float rad, entity cantbe, entity mustbe,
887 float inflictorselfdamage, float forceintensity, float forcezscale, int deathtype, .entity weaponentity, entity directhitentity)
888 // Returns total damage applies to creatures
892 float total_damage_to_creatures;
897 float stat_damagedone;
899 if(RadiusDamage_running)
901 backtrace("RadiusDamage called recursively! Expect stuff to go HORRIBLY wrong.");
905 RadiusDamage_running = 1;
907 tfloordmg = autocvar_g_throughfloor_damage;
908 tfloorforce = autocvar_g_throughfloor_force;
910 total_damage_to_creatures = 0;
912 if(deathtype != (WEP_HOOK.m_id | HITTYPE_SECONDARY | HITTYPE_BOUNCE)) // only send gravity bomb damage once
913 if(DEATH_WEAPONOF(deathtype) != WEP_TUBA) // do not send tuba damage (bandwidth hog)
915 force = inflictorvelocity;
919 force = normalize(force);
920 if(forceintensity >= 0)
921 Damage_DamageInfo(inflictororigin, coredamage, edgedamage, rad, forceintensity * force, deathtype, 0, attacker);
923 Damage_DamageInfo(inflictororigin, coredamage, edgedamage, -rad, (-forceintensity) * force, deathtype, 0, attacker);
928 targ = WarpZone_FindRadius (inflictororigin, rad + MAX_DAMAGEEXTRARADIUS, false);
932 if ((targ != inflictor) || inflictorselfdamage)
933 if (((cantbe != targ) && !mustbe) || (mustbe == targ))
940 // LordHavoc: measure distance to nearest point on target (not origin)
941 // (this guarentees 100% damage on a touch impact)
942 nearest = targ.WarpZone_findradius_nearest;
943 diff = targ.WarpZone_findradius_dist;
944 // round up a little on the damage to ensure full damage on impacts
945 // and turn the distance into a fraction of the radius
946 power = 1 - ((vlen (diff) - bound(MIN_DAMAGEEXTRARADIUS, targ.damageextraradius, MAX_DAMAGEEXTRARADIUS)) / rad);
948 //bprint(ftos(power));
949 //if (targ == attacker)
950 // print(ftos(power), "\n");
956 finaldmg = coredamage * power + edgedamage * (1 - power);
962 vector myblastorigin;
965 myblastorigin = WarpZone_TransformOrigin(targ, inflictororigin);
967 // if it's a player, use the view origin as reference
968 center = CENTER_OR_VIEWOFS(targ);
970 force = normalize(center - myblastorigin);
971 force = force * (finaldmg / coredamage) * forceintensity;
974 // apply special scaling along the z axis if set
975 // NOTE: 0 value is not allowed for compatibility, in the case of weapon cvars not being set
977 force.z *= forcezscale;
979 if(targ != directhitentity)
984 float mininv_f, mininv_d;
986 // test line of sight to multiple positions on box,
987 // and do damage if any of them hit
990 // we know: max stddev of hitratio = 1 / (2 * sqrt(n))
991 // so for a given max stddev:
992 // n = (1 / (2 * max stddev of hitratio))^2
994 mininv_d = (finaldmg * (1-tfloordmg)) / autocvar_g_throughfloor_damage_max_stddev;
995 mininv_f = (vlen(force) * (1-tfloorforce)) / autocvar_g_throughfloor_force_max_stddev;
997 if(autocvar_g_throughfloor_debug)
998 LOG_INFOF("THROUGHFLOOR: D=%f F=%f max(dD)=1/%f max(dF)=1/%f", finaldmg, vlen(force), mininv_d, mininv_f);
1001 total = 0.25 * (max(mininv_f, mininv_d) ** 2);
1003 if(autocvar_g_throughfloor_debug)
1004 LOG_INFOF(" steps=%f", total);
1007 if (IS_PLAYER(targ))
1008 total = ceil(bound(autocvar_g_throughfloor_min_steps_player, total, autocvar_g_throughfloor_max_steps_player));
1010 total = ceil(bound(autocvar_g_throughfloor_min_steps_other, total, autocvar_g_throughfloor_max_steps_other));
1012 if(autocvar_g_throughfloor_debug)
1013 LOG_INFOF(" steps=%f dD=%f dF=%f", total, finaldmg * (1-tfloordmg) / (2 * sqrt(total)), vlen(force) * (1-tfloorforce) / (2 * sqrt(total)));
1015 for(c = 0; c < total; ++c)
1017 //traceline(targ.WarpZone_findradius_findorigin, nearest, MOVE_NOMONSTERS, inflictor);
1018 WarpZone_TraceLine(inflictororigin, WarpZone_UnTransformOrigin(targ, nearest), MOVE_NOMONSTERS, inflictor);
1019 if (trace_fraction == 1 || trace_ent == targ)
1023 hitloc = hitloc + nearest;
1027 nearest.x = targ.origin.x + targ.mins.x + random() * targ.size.x;
1028 nearest.y = targ.origin.y + targ.mins.y + random() * targ.size.y;
1029 nearest.z = targ.origin.z + targ.mins.z + random() * targ.size.z;
1032 nearest = hitloc * (1 / max(1, hits));
1033 hitratio = (hits / total);
1034 a = bound(0, tfloordmg + (1-tfloordmg) * hitratio, 1);
1035 finaldmg = finaldmg * a;
1036 a = bound(0, tfloorforce + (1-tfloorforce) * hitratio, 1);
1039 if(autocvar_g_throughfloor_debug)
1040 LOG_INFOF(" D=%f F=%f", finaldmg, vlen(force));
1043 //if (targ == attacker)
1045 // print("hits ", ftos(hits), " / ", ftos(total));
1046 // print(" finaldmg ", ftos(finaldmg), " force ", vtos(force));
1047 // print(" (", ftos(a), ")\n");
1049 if(finaldmg || force)
1053 total_damage_to_creatures += finaldmg;
1055 if(accuracy_isgooddamage(attacker, targ))
1056 stat_damagedone += finaldmg;
1059 if(targ == directhitentity || DEATH_ISSPECIAL(deathtype))
1060 Damage(targ, inflictor, attacker, finaldmg, deathtype, weaponentity, nearest, force);
1062 Damage(targ, inflictor, attacker, finaldmg, deathtype | HITTYPE_SPLASH, weaponentity, nearest, force);
1070 RadiusDamage_running = 0;
1072 if(!DEATH_ISSPECIAL(deathtype))
1073 accuracy_add(attacker, DEATH_WEAPONOF(deathtype), 0, min(coredamage, stat_damagedone));
1075 return total_damage_to_creatures;
1078 float RadiusDamage(entity inflictor, entity attacker, float coredamage, float edgedamage, float rad, entity cantbe, entity mustbe, float forceintensity, int deathtype, .entity weaponentity, entity directhitentity)
1080 return RadiusDamageForSource(inflictor, (inflictor.origin + (inflictor.mins + inflictor.maxs) * 0.5), inflictor.velocity, attacker, coredamage, edgedamage, rad,
1081 cantbe, mustbe, false, forceintensity, 1, deathtype, weaponentity, directhitentity);
1084 bool Heal(entity targ, entity inflictor, float amount, float limit)
1086 if(game_stopped || (IS_CLIENT(targ) && CS(targ).killcount == FRAGS_SPECTATOR) || STAT(FROZEN, targ) || IS_DEAD(targ))
1089 bool healed = false;
1091 healed = targ.event_heal(targ, inflictor, amount, limit);
1092 // TODO: additional handling? what if the healing kills them? should this abort if healing would do so etc
1093 // TODO: healing fx!
1094 // TODO: armor healing?
1098 float Fire_IsBurning(entity e)
1100 return (time < e.fire_endtime);
1103 float Fire_AddDamage(entity e, entity o, float d, float t, float dt)
1106 float maxtime, mintime, maxdamage, mindamage, maxdps, mindps, totaldamage, totaltime;
1117 // print("adding a fire burner to ", e.classname, "\n");
1118 e.fire_burner = new(fireburner);
1119 setthink(e.fire_burner, fireburner_think);
1120 e.fire_burner.nextthink = time;
1121 e.fire_burner.owner = e;
1127 if(Fire_IsBurning(e))
1129 mintime = e.fire_endtime - time;
1130 maxtime = max(mintime, t);
1132 mindps = e.fire_damagepersec;
1133 maxdps = max(mindps, dps);
1135 if(maxtime > mintime || maxdps > mindps)
1139 // damage we have right now
1140 mindamage = mindps * mintime;
1142 // damage we want to get
1143 maxdamage = mindamage + d;
1145 // but we can't exceed maxtime * maxdps!
1146 totaldamage = min(maxdamage, maxtime * maxdps);
1150 // totaldamage = min(mindamage + d, maxtime * maxdps)
1152 // totaldamage <= maxtime * maxdps
1153 // ==> totaldamage / maxdps <= maxtime.
1155 // totaldamage / mindps = min(mindamage / mindps + d, maxtime * maxdps / mindps)
1156 // >= min(mintime, maxtime)
1157 // ==> totaldamage / maxdps >= mintime.
1160 // how long do we damage then?
1161 // at least as long as before
1162 // but, never exceed maxdps
1163 totaltime = max(mintime, totaldamage / maxdps); // always <= maxtime due to lemma
1167 // at most as long as maximum allowed
1168 // but, never below mindps
1169 totaltime = min(maxtime, totaldamage / mindps); // always >= mintime due to lemma
1171 // assuming t > mintime, dps > mindps:
1172 // we get d = t * dps = maxtime * maxdps
1173 // totaldamage = min(maxdamage, maxtime * maxdps) = min(... + d, maxtime * maxdps) = maxtime * maxdps
1174 // totaldamage / maxdps = maxtime
1175 // totaldamage / mindps > totaldamage / maxdps = maxtime
1177 // a) totaltime = max(mintime, maxtime) = maxtime
1178 // b) totaltime = min(maxtime, totaldamage / maxdps) = maxtime
1180 // assuming t <= mintime:
1181 // we get maxtime = mintime
1182 // a) totaltime = max(mintime, ...) >= mintime, also totaltime <= maxtime by the lemma, therefore totaltime = mintime = maxtime
1183 // b) totaltime = min(maxtime, ...) <= maxtime, also totaltime >= mintime by the lemma, therefore totaltime = mintime = maxtime
1185 // assuming dps <= mindps:
1186 // we get mindps = maxdps.
1187 // With this, the lemma says that mintime <= totaldamage / mindps = totaldamage / maxdps <= maxtime.
1188 // a) totaltime = max(mintime, totaldamage / maxdps) = totaldamage / maxdps
1189 // b) totaltime = min(maxtime, totaldamage / mindps) = totaldamage / maxdps
1191 e.fire_damagepersec = totaldamage / totaltime;
1192 e.fire_endtime = time + totaltime;
1193 if(totaldamage > 1.2 * mindamage)
1195 e.fire_deathtype = dt;
1196 if(e.fire_owner != o)
1199 e.fire_hitsound = false;
1202 if(accuracy_isgooddamage(o, e))
1203 accuracy_add(o, DEATH_WEAPONOF(dt), 0, max(0, totaldamage - mindamage));
1204 return max(0, totaldamage - mindamage); // can never be negative, but to make sure
1211 e.fire_damagepersec = dps;
1212 e.fire_endtime = time + t;
1213 e.fire_deathtype = dt;
1215 e.fire_hitsound = false;
1216 if(accuracy_isgooddamage(o, e))
1217 accuracy_add(o, DEATH_WEAPONOF(dt), 0, d);
1222 void Fire_ApplyDamage(entity e)
1227 if (!Fire_IsBurning(e))
1230 for(t = 0, o = e.owner; o.owner && t < 16; o = o.owner, ++t);
1231 if(IS_NOT_A_CLIENT(o))
1234 // water and slime stop fire
1236 if(e.watertype != CONTENT_LAVA)
1243 t = min(frametime, e.fire_endtime - time);
1244 d = e.fire_damagepersec * t;
1246 hi = e.fire_owner.damage_dealt;
1247 ty = e.fire_owner.typehitsound;
1248 Damage(e, e, e.fire_owner, d, e.fire_deathtype, DMG_NOWEP, e.origin, '0 0 0');
1249 if(e.fire_hitsound && e.fire_owner)
1251 e.fire_owner.damage_dealt = hi;
1252 e.fire_owner.typehitsound = ty;
1254 e.fire_hitsound = true;
1256 if(!IS_INDEPENDENT_PLAYER(e) && !STAT(FROZEN, e))
1258 IL_EACH(g_damagedbycontents, it.damagedbycontents && it != e,
1260 if(!IS_DEAD(it) && it.takedamage && !IS_INDEPENDENT_PLAYER(it))
1261 if(boxesoverlap(e.absmin, e.absmax, it.absmin, it.absmax))
1263 t = autocvar_g_balance_firetransfer_time * (e.fire_endtime - time);
1264 d = autocvar_g_balance_firetransfer_damage * e.fire_damagepersec * t;
1265 Fire_AddDamage(it, o, d, t, DEATH_FIRE.m_id);
1271 void Fire_ApplyEffect(entity e)
1273 if(Fire_IsBurning(e))
1274 e.effects |= EF_FLAME;
1276 e.effects &= ~EF_FLAME;
1279 void fireburner_think(entity this)
1281 // for players, this is done in the regular loop
1282 if(wasfreed(this.owner))
1287 Fire_ApplyEffect(this.owner);
1288 if(!Fire_IsBurning(this.owner))
1290 this.owner.fire_burner = NULL;
1294 Fire_ApplyDamage(this.owner);
1295 this.nextthink = time;