Xonotic QuakeC
The free, fast arena FPS with crisp movement and a wide array of weapons
damage.qc File Reference
Include dependency graph for damage.qc:
This graph shows which files directly or indirectly include this file:

Go to the source code of this file.

Macros

#define SPREE_ITEM(counta, countb, center, normal, gentle)

Functions

string AppendItemcodes (string s, entity player)
void Damage (entity targ, entity inflictor, entity attacker, float damage, int deathtype,.entity weaponentity, vector hitloc, vector force)
float Fire_AddDamage (entity e, entity o, float d, float t, float dt)
void Fire_ApplyDamage (entity e)
bool frag_centermessage_override (entity attacker, entity targ, int deathtype, int kill_count_to_attacker, int kill_count_to_target, string attacker_name)
void GiveFrags (entity attacker, entity targ, float f, int deathtype,.entity weaponentity)
bool Heal (entity targ, entity inflictor, float amount, float limit)
void LogDeath (string mode, int deathtype, entity killer, entity killed)
void Obituary (entity attacker, entity inflictor, entity targ, int deathtype,.entity weaponentity)
void Obituary_SpecialDeath (entity notif_target, bool murder, bool msg_from_ent, int deathtype, string s1, string s2, string s3, string s4, float f1, float f2, float f3)
float Obituary_WeaponDeath (entity notif_target, bool murder, int deathtype, string s1, string s2, string s3, float f1, float f2)
float RadiusDamage (entity inflictor, entity attacker, float coredamage, float edgedamage, float rad, entity cantbe, entity mustbe, float forceintensity, int deathtype,.entity weaponentity, entity directhitentity)
float RadiusDamageForSource (entity inflictor, vector inflictororigin, vector inflictorvelocity, entity attacker, float coredamage, float edgedamage, float rad, entity cantbe, entity mustbe, bool inflictorselfdamage, float forceintensity, vector forcexyzscale, int deathtype,.entity weaponentity, entity directhitentity)

Macro Definition Documentation

◆ SPREE_ITEM

#define SPREE_ITEM ( counta,
countb,
center,
normal,
gentle )
Value:
case counta: \
Send_Notification(NOTIF_ONE, attacker, MSG_ANNCE, ANNCE_KILLSTREAK_##countb); \
if (!warmup_stage) \
PlayerStats_GameReport_Event_Player(attacker, PLAYERSTATS_ACHIEVEMENT_KILL_SPREE_##counta, 1); \
break;
bool warmup_stage
Definition main.qh:120

Function Documentation

◆ AppendItemcodes()

string AppendItemcodes ( string s,
entity player )

Definition at line 82 of file damage.qc.

83{
84 for (int slot = 0; slot < MAX_WEAPONSLOTS; ++slot)
85 {
86 .entity weaponentity = weaponentities[slot];
87 int w = player.(weaponentity).m_weapon.m_id;
88 if (w == 0)
89 w = player.(weaponentity).cnt; // previous weapon
90 if (w != 0 || slot == 0)
91 s = strcat(s, ftos(w));
92 }
93 if (PHYS_INPUT_BUTTON_CHAT(player))
94 s = strcat(s, "T");
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);
98 return s;
99}
#define MUTATOR_CALLHOOK(id,...)
Definition base.qh:143
float cnt
Definition powerups.qc:24
#define M_ARGV(x, type)
Definition events.qh:17
#define PHYS_INPUT_BUTTON_CHAT(s)
Definition player.qh:161
string ftos(float f)
strcat(_("^F4Countdown stopped!"), "\n^BG", _("Teams are too unbalanced."))
const int MAX_WEAPONSLOTS
Definition weapon.qh:16
entity weaponentities[MAX_WEAPONSLOTS]
Definition weapon.qh:17
Weapon m_weapon
Definition wepent.qh:26

References cnt, entity(), ftos(), M_ARGV, m_weapon, MAX_WEAPONSLOTS, MUTATOR_CALLHOOK, PHYS_INPUT_BUTTON_CHAT, strcat(), and weaponentities.

Referenced by LogDeath().

◆ Damage()

void Damage ( entity targ,
entity inflictor,
entity attacker,
float damage,
int deathtype,
.entity weaponentity,
vector hitloc,
vector force )

Definition at line 484 of file damage.qc.

485{
486 if (game_stopped || (IS_CLIENT(targ) && CS(targ).killcount == FRAGS_SPECTATOR))
487 return;
488
489 // special rule: gravity bombs and sound-based attacks do not affect team mates (other than for disconnecting the hook)
490 if ((DEATH_ISWEAPON(deathtype, WEP_HOOK) || (deathtype & HITTYPE_SOUND))
491 && IS_PLAYER(targ) && SAME_TEAM(targ, attacker))
492 return;
493
494 entity attacker_save = attacker;
495
496 float complainteamdamage = 0;
497 float mirrordamage = 0;
498 float mirrorforce = 0;
499
500 if (deathtype == DEATH_KILL.m_id || deathtype == DEATH_TEAMCHANGE.m_id || deathtype == DEATH_AUTOTEAMCHANGE.m_id || damage == INFINITY)
501 {
502 // exit the vehicle before killing (fixes a crash)
503 if (IS_PLAYER(targ) && targ.vehicle)
504 vehicles_exit(targ.vehicle, VHEF_RELEASE);
505
506 StatusEffects_remove(STATUSEFFECT_SpawnShield, targ, STATUSEFFECT_REMOVE_CLEAR);
507 targ.flags &= ~FL_GODMODE;
508 damage = INFINITY;
509 }
510 else if (deathtype == DEATH_MIRRORDAMAGE.m_id || deathtype == DEATH_NOAMMO.m_id)
511 {
512 // no processing
513 }
514 else
515 {
516 // nullify damage if teamplay is on
517 if (deathtype != DEATH_TELEFRAG.m_id
518 && IS_PLAYER(attacker))
519 {
520 // avoid dealing damage or force to other independent players
521 // and avoid dealing damage or force to things owned by other independent players
522 if ((IS_PLAYER(targ) && targ != attacker && (IS_INDEPENDENT_PLAYER(attacker) || IS_INDEPENDENT_PLAYER(targ)))
523 || (targ.realowner && IS_INDEPENDENT_PLAYER(targ.realowner) && attacker != targ.realowner))
524 {
525 damage = 0;
526 force = '0 0 0';
527 }
528 else if (!STAT(FROZEN, targ) && SAME_TEAM(attacker, targ))
529 {
530 if (autocvar_teamplay_mode == 1)
531 damage = 0;
532 else if (attacker != targ)
533 {
534 if (autocvar_teamplay_mode == 2)
535 {
536 if (IS_PLAYER(targ) && !IS_DEAD(targ))
537 {
538 attacker.dmg_team += damage;
539 complainteamdamage = attacker.dmg_team - autocvar_g_teamdamage_threshold;
540 }
541 }
542 else if (autocvar_teamplay_mode == 3)
543 damage = 0;
544 else if (autocvar_teamplay_mode == 4)
545 {
546 if (IS_PLAYER(targ) && !IS_DEAD(targ))
547 {
548 attacker.dmg_team += damage;
549 complainteamdamage = attacker.dmg_team - autocvar_g_teamdamage_threshold;
550 if (complainteamdamage > 0)
551 mirrordamage = autocvar_g_mirrordamage * complainteamdamage;
552 mirrorforce = autocvar_g_mirrordamage * vlen(force);
553 damage = autocvar_g_friendlyfire * damage;
554 // mirrordamage will be used LATER
555
557 {
558 vector v = healtharmor_applydamage(GetResource(attacker, RES_HEALTH), GetResource(attacker, RES_ARMOR),
559 autocvar_g_balance_armor_blockpercent, deathtype, mirrordamage);
560 attacker.dmg_take += v.x;
561 attacker.dmg_save += v.y;
562 attacker.dmg_inflictor = inflictor;
563 mirrordamage = v.z;
564 mirrorforce = 0;
565 }
566
568 {
569 vector v = healtharmor_applydamage(GetResource(targ, RES_HEALTH), GetResource(targ, RES_ARMOR),
570 autocvar_g_balance_armor_blockpercent, deathtype, damage);
571 targ.dmg_take += v.x;
572 targ.dmg_save += v.y;
573 targ.dmg_inflictor = inflictor;
574 damage = 0;
576 force = '0 0 0';
577 }
578 }
579 else if (!targ.canteamdamage)
580 damage = 0;
581 }
582 }
583 }
584 }
585
586 if (!DEATH_ISSPECIAL(deathtype))
587 {
589 mirrordamage *= autocvar_g_weapondamagefactor;
590 complainteamdamage *= autocvar_g_weapondamagefactor;
592 mirrorforce *= autocvar_g_weaponforcefactor;
593 }
594
595 // should this be changed at all? If so, in what way?
596 MUTATOR_CALLHOOK(Damage_Calculate, inflictor, attacker, targ, deathtype, damage, mirrordamage, force, attacker.(weaponentity));
597 damage = M_ARGV(4, float);
598 mirrordamage = M_ARGV(5, float);
599 force = M_ARGV(6, vector);
600
601 if (IS_PLAYER(targ) && damage > 0 && attacker)
602 for (int slot = 0; slot < MAX_WEAPONSLOTS; ++slot)
603 {
604 .entity went = weaponentities[slot];
605 if (targ.(went).hook && targ.(went).hook.aiment == attacker)
606 RemoveHook(targ.(went).hook);
607 }
608
609 if (targ == attacker)
610 damage *= autocvar_g_balance_selfdamagepercent; // Partial damage if the attacker hits himself
611
612 // count the damage
613 if (attacker && targ != attacker
614 && !IS_DEAD(targ)
615 && deathtype != DEATH_BUFF_INFERNO.m_id
616 && deathtype != DEATH_BUFF_VENGEANCE.m_id
617 && targ.takedamage == DAMAGE_AIM)
618 {
619 entity victim;
620 if (IS_VEHICLE(targ) && targ.owner)
621 victim = targ.owner;
622 else
623 victim = targ;
624
625 // TODO: allow the mutator hook to tell if the hit sound should be team or not
626 if (IS_PLAYER(victim) || (IS_TURRET(victim) && victim.active == ACTIVE_ACTIVE) || IS_MONSTER(victim)
627 || MUTATOR_CALLHOOK(PlayHitsound, victim, attacker))
628 {
629 if (DIFF_TEAM(victim, attacker))
630 {
631 if (damage > 0)
632 {
633 if (deathtype != DEATH_FIRE.m_id)
634 {
635 if (PHYS_INPUT_BUTTON_CHAT(victim))
636 ++attacker.typehitsound;
637 else
638 attacker.hitsound_damage_dealt += damage;
639 }
640
642
643 if (!DEATH_ISSPECIAL(deathtype)
644 && IS_PLAYER(targ) // don't do this for vehicles
645 && IsFlying(victim))
646 yoda = 1;
647 }
648 }
649 else if (IS_PLAYER(attacker) && !STAT(FROZEN, victim) && !(deathtype & HITTYPE_SPAM)) // same team
650 {
651 if (deathtype != DEATH_FIRE.m_id)
652 ++attacker.typehitsound;
653 if (complainteamdamage > 0
654 && time > CS(attacker).teamkill_complain)
655 {
656 CS(attacker).teamkill_complain = time + 5;
657 CS(attacker).teamkill_soundtime = time + 0.4;
658 CS(attacker).teamkill_soundsource = targ;
659 }
660 }
661 }
662 }
663 }
664
665 // apply push
666 if (targ.damageforcescale && force
667 && (!IS_PLAYER(targ) || !StatusEffects_active(STATUSEFFECT_SpawnShield, targ) || targ == attacker))
668 {
669 vector farce = damage_explosion_calcpush(targ.damageforcescale * force, targ.velocity, autocvar_g_balance_damagepush_speedfactor);
670 if (targ.move_movetype == MOVETYPE_PHYSICS)
671 {
672 entity farcent = new(farce);
673 farcent.enemy = targ;
674 farcent.movedir = farce * 10;
675 if (targ.mass)
676 farcent.movedir *= targ.mass;
677 farcent.origin = hitloc;
678 farcent.forcetype = FORCETYPE_FORCEATPOS;
679 farcent.nextthink = time + 0.1;
680 setthink(farcent, SUB_Remove);
681 }
682 else if (targ.move_movetype != MOVETYPE_NOCLIP)
683 targ.velocity += farce;
684 UNSET_ONGROUND(targ);
686 }
687 // apply damage
688 if ((damage != 0 || (targ.damageforcescale && force))
689 && targ.event_damage)
690 targ.event_damage(targ, inflictor, attacker, damage, deathtype, weaponentity, hitloc, force);
691
692 // apply mirror damage if any
693 if ((!autocvar_g_mirrordamage_onlyweapons || DEATH_WEAPONOF(deathtype) != WEP_Null)
694 && (mirrordamage > 0 || mirrorforce > 0))
695 {
696 attacker = attacker_save;
697
698 force = normalize(attacker.origin + attacker.view_ofs - hitloc) * mirrorforce;
699 Damage(attacker, inflictor, attacker, mirrordamage, DEATH_MIRRORDAMAGE.m_id, weaponentity, attacker.origin, force);
700 }
701}
vector damage_explosion_calcpush(vector explosion_f, vector target_v, float speedfactor)
var entity(vector mins, vector maxs,.entity tofield) findbox_tofield_OrFallback
float GetResource(entity e, Resource res_type)
Returns the current amount of resource the given entity has.
bool IsFlying(entity this)
Definition player.qc:843
#define IS_CLIENT(s)
Definition player.qh:241
#define IS_DEAD(s)
Definition player.qh:244
#define IS_PLAYER(s)
Definition player.qh:242
float game_stopped
Definition stats.qh:81
vector healtharmor_applydamage(float h, float a, float armorblock, int deathtype, float damage)
Definition util.qc:1425
const int FL_GODMODE
Definition constants.qh:67
const int FRAGS_SPECTATOR
engine expects this value
Definition constants.qh:5
float time
void UpdateCSQCProjectile(entity e)
void Damage(entity targ, entity inflictor, entity attacker, float damage, int deathtype,.entity weaponentity, vector hitloc, vector force)
Definition damage.qc:484
bool autocvar_g_mirrordamage_virtual
Definition damage.qh:15
bool autocvar_g_mirrordamage_onlyweapons
Definition damage.qh:16
float yoda
Definition damage.qh:48
float autocvar_g_friendlyfire_virtual_force
Definition damage.qh:28
float autocvar_g_balance_armor_blockpercent
Definition damage.qh:21
int impressive_hits
Definition damage.qh:49
float autocvar_g_balance_selfdamagepercent
Definition damage.qh:25
float autocvar_g_friendlyfire_virtual
Definition damage.qh:27
float autocvar_g_teamdamage_threshold
Definition damage.qh:23
float teamkill_complain
Definition damage.qh:54
float autocvar_g_friendlyfire
Definition damage.qh:26
float autocvar_g_balance_damagepush_speedfactor
Definition damage.qh:18
float autocvar_g_mirrordamage
Definition damage.qh:14
#define DEATH_ISWEAPON(t, w)
Definition all.qh:48
#define DEATH_ISSPECIAL(t)
Definition all.qh:41
const int HITTYPE_SPAM
set manually after first RadiusDamage, stops effect spam
Definition all.qh:36
#define DEATH_WEAPONOF(t)
Definition all.qh:47
const int HITTYPE_SOUND
cause bleeding from ears
Definition all.qh:35
void SUB_Remove(entity this)
Remove entity.
Definition defer.qh:12
const int ACTIVE_ACTIVE
Definition defs.qh:37
const float FORCETYPE_FORCEATPOS
RES_ARMOR
Definition ent_cs.qc:155
#define STAT(...)
Definition stats.qh:94
#define INFINITY
Definition mathlib.qh:105
float vlen(vector v)
vector normalize(vector v)
#define UNSET_ONGROUND(s)
Definition movetypes.qh:18
const int MOVETYPE_PHYSICS
Definition movetypes.qh:146
const int MOVETYPE_NOCLIP
Definition movetypes.qh:141
@ STATUSEFFECT_REMOVE_CLEAR
Effect is being forcibly removed without calling any additional mechanics.
Definition all.qh:30
#define setthink(e, f)
vector
Definition self.qh:96
int killcount
Definition client.qh:315
#define IS_INDEPENDENT_PLAYER(e)
Definition client.qh:312
void RemoveHook(entity this)
Definition hook.qc:48
ClientState CS(Client this)
Definition state.qh:47
void StatusEffects_remove(StatusEffect this, entity actor, int removal_type)
bool StatusEffects_active(StatusEffect this, entity actor)
const int DAMAGE_AIM
Definition subs.qh:81
void vehicles_exit(entity vehic, bool eject)
const int VHEF_RELEASE
Release ownership, client possibly allready dissconnected / went spec / changed team / used "kill" (n...
int autocvar_teamplay_mode
Definition teamplay.qh:3
#define SAME_TEAM(a, b)
Definition teams.qh:241
#define DIFF_TEAM(a, b)
Definition teams.qh:242
#define IS_TURRET(v)
Definition utils.qh:25
#define IS_MONSTER(v)
Definition utils.qh:23
#define IS_VEHICLE(v)
Definition utils.qh:24
float autocvar_g_weaponforcefactor
float autocvar_g_weapondamagefactor

References ACTIVE_ACTIVE, autocvar_g_balance_armor_blockpercent, autocvar_g_balance_damagepush_speedfactor, autocvar_g_balance_selfdamagepercent, autocvar_g_friendlyfire, autocvar_g_friendlyfire_virtual, autocvar_g_friendlyfire_virtual_force, autocvar_g_mirrordamage, autocvar_g_mirrordamage_onlyweapons, autocvar_g_mirrordamage_virtual, autocvar_g_teamdamage_threshold, autocvar_g_weapondamagefactor, autocvar_g_weaponforcefactor, autocvar_teamplay_mode, CS(), Damage(), DAMAGE_AIM, damage_explosion_calcpush(), DEATH_ISSPECIAL, DEATH_ISWEAPON, DEATH_WEAPONOF, DIFF_TEAM, entity(), FL_GODMODE, FORCETYPE_FORCEATPOS, FRAGS_SPECTATOR, game_stopped, GetResource(), healtharmor_applydamage(), HITTYPE_SOUND, HITTYPE_SPAM, impressive_hits, INFINITY, IS_CLIENT, IS_DEAD, IS_INDEPENDENT_PLAYER, IS_MONSTER, IS_PLAYER, IS_TURRET, IS_VEHICLE, IsFlying(), killcount, M_ARGV, MAX_WEAPONSLOTS, MOVETYPE_NOCLIP, MOVETYPE_PHYSICS, MUTATOR_CALLHOOK, normalize(), PHYS_INPUT_BUTTON_CHAT, RemoveHook(), RES_ARMOR, SAME_TEAM, setthink, STAT, STATUSEFFECT_REMOVE_CLEAR, StatusEffects_active(), StatusEffects_remove(), SUB_Remove(), teamkill_complain, time, UNSET_ONGROUND, UpdateCSQCProjectile(), vector, vehicles_exit(), VHEF_RELEASE, vlen(), weaponentities, and yoda.

Referenced by buff_Vengeance_DelayedDamage(), bumblebee_pilot_frame(), ClientKill_Now(), CommonCommand_editmob(), CreatureFrame_FallDamage(), CreatureFrame_hotliquids(), ctf_CaptureShield_Touch(), Damage(), door_blocked(), door_generic_plat_blocked(), DrownPlayer(), Fire_ApplyDamage(), fireBullet_falloff(), generic_plat_blocked(), havocbot_role_ctf_carrier(), instagib_countdown(), M_Zombie_Attack_Leap_Touch(), misc_laser_think(), Monster_Attack_Melee(), Monster_Move(), Monster_Think(), MUTATOR_HOOKFUNCTION(), MUTATOR_HOOKFUNCTION(), MUTATOR_HOOKFUNCTION(), MUTATOR_HOOKFUNCTION(), nade_heal_touch(), nade_spawn_DestroyDamage(), nade_translocate_DestroyDamage(), ons_CaptureShield_Touch(), Onslaught_CheckWinner(), plat_crush(), RadiusDamageForSource(), SetPlayerTeam(), target_kill_use(), tdeath(), trigger_hurt_touch(), vehicles_impact(), vehicles_touch(), W_Arc_Beam_Think(), W_Fireball_Explode(), W_OverkillRocketPropelledChainsaw_Think(), W_Shotgun_Melee_Think(), and walker_melee_do_dmg().

◆ Fire_AddDamage()

float Fire_AddDamage ( entity e,
entity o,
float d,
float t,
float dt )

Definition at line 960 of file damage.qc.

961{
962 if (d <= 0)
963 return -1;
964 if (IS_PLAYER(e) && IS_DEAD(e))
965 return -1;
966
967 t = max(t, 0.1);
968 float dps = d / t;
969
970 if (StatusEffects_active(STATUSEFFECT_Burning, e))
971 {
972 float fireendtime = StatusEffects_gettime(STATUSEFFECT_Burning, e);
973
974 float mintime = fireendtime - time;
975 float maxtime = max(mintime, t);
976
977 float mindps = e.fire_damagepersec;
978 float maxdps = max(mindps, dps);
979
980 if (maxtime > mintime || maxdps > mindps)
981 {
982 // Constraints:
983
984 // damage we have right now
985 float mindamage = mindps * mintime;
986
987 // damage we want to get
988 float maxdamage = mindamage + d;
989
990 // but we can't exceed maxtime * maxdps!
991 float totaldamage = min(maxdamage, maxtime * maxdps);
992
993 // LEMMA:
994 // Look at:
995 // totaldamage = min(mindamage + d, maxtime * maxdps)
996 // We see:
997 // totaldamage <= maxtime * maxdps
998 // ==> totaldamage / maxdps <= maxtime.
999 // We also see:
1000 // totaldamage / mindps = min(mindamage / mindps + d, maxtime * maxdps / mindps)
1001 // >= min(mintime, maxtime)
1002 // ==> totaldamage / maxdps >= mintime.
1003
1004 /*
1005 // how long do we damage then?
1006 // at least as long as before
1007 // but, never exceed maxdps
1008 totaltime = max(mintime, totaldamage / maxdps); // always <= maxtime due to lemma
1009 */
1010
1011 // alternate:
1012 // at most as long as maximum allowed
1013 // but, never below mindps
1014 float totaltime = min(maxtime, totaldamage / mindps); // always >= mintime due to lemma
1015
1016 // assuming t > mintime, dps > mindps:
1017 // we get d = t * dps = maxtime * maxdps
1018 // totaldamage = min(maxdamage, maxtime * maxdps) = min(... + d, maxtime * maxdps) = maxtime * maxdps
1019 // totaldamage / maxdps = maxtime
1020 // totaldamage / mindps > totaldamage / maxdps = maxtime
1021 // FROM THIS:
1022 // a) totaltime = max(mintime, maxtime) = maxtime
1023 // b) totaltime = min(maxtime, totaldamage / maxdps) = maxtime
1024
1025 // assuming t <= mintime:
1026 // we get maxtime = mintime
1027 // a) totaltime = max(mintime, ...) >= mintime, also totaltime <= maxtime by the lemma, therefore totaltime = mintime = maxtime
1028 // b) totaltime = min(maxtime, ...) <= maxtime, also totaltime >= mintime by the lemma, therefore totaltime = mintime = maxtime
1029
1030 // assuming dps <= mindps:
1031 // we get mindps = maxdps.
1032 // With this, the lemma says that mintime <= totaldamage / mindps = totaldamage / maxdps <= maxtime.
1033 // a) totaltime = max(mintime, totaldamage / maxdps) = totaldamage / maxdps
1034 // b) totaltime = min(maxtime, totaldamage / mindps) = totaldamage / maxdps
1035
1036 e.fire_damagepersec = totaldamage / totaltime;
1037 StatusEffects_apply(STATUSEFFECT_Burning, e, time + totaltime, 0);
1038 if (totaldamage > 1.2 * mindamage)
1039 {
1040 e.fire_deathtype = dt;
1041 if (e.fire_owner != o)
1042 {
1043 e.fire_owner = o;
1044 e.fire_hitsound = false;
1045 }
1046 }
1047 if (accuracy_isgooddamage(o, e))
1048 accuracy_add(o, DEATH_WEAPONOF(dt), 0, max(0, totaldamage - mindamage), 0); // add to hit
1049 return max(0, totaldamage - mindamage); // can never be negative, but to make sure
1050 }
1051 else
1052 return 0;
1053 }
1054 else
1055 {
1056 e.fire_damagepersec = dps;
1057 StatusEffects_apply(STATUSEFFECT_Burning, e, time + t, 0);
1058 e.fire_deathtype = dt;
1059 e.fire_owner = o;
1060 e.fire_hitsound = false;
1061 if (accuracy_isgooddamage(o, e))
1062 accuracy_add(o, DEATH_WEAPONOF(dt), 0, d, 0); // add to hit
1063 return d;
1064 }
1065}
void accuracy_add(entity this, Weapon w, float fired, float hit, float real)
update accuracy stats
Definition accuracy.qc:102
bool accuracy_isgooddamage(entity attacker, entity targ)
does this damage count towards accuracy stats?
Definition accuracy.qc:133
float min(float f,...)
float max(float f,...)
float StatusEffects_gettime(StatusEffect this, entity actor)
void StatusEffects_apply(StatusEffect this, entity actor, float eff_time, int eff_flags)

References accuracy_add(), accuracy_isgooddamage(), DEATH_WEAPONOF, entity(), IS_DEAD, IS_PLAYER, max(), min(), StatusEffects_active(), StatusEffects_apply(), StatusEffects_gettime(), and time.

Referenced by CreatureFrame_hotliquids(), M_Wyvern_Attack_Fireball_Explode(), MUTATOR_HOOKFUNCTION(), napalm_damage(), W_Fireball_Firemine_Touch(), and W_Fireball_LaserPlay().

◆ Fire_ApplyDamage()

void Fire_ApplyDamage ( entity e)

Definition at line 1067 of file damage.qc.

1068{
1069 entity o;
1070 float t;
1071 for (t = 0, o = e.owner; o.owner && t < 16; o = o.owner, ++t);
1072 if (IS_NOT_A_CLIENT(o))
1073 o = e.fire_owner;
1074
1075 float fireendtime = StatusEffects_gettime(STATUSEFFECT_Burning, e);
1076 t = min(frametime, fireendtime - time);
1077 float d = e.fire_damagepersec * t;
1078
1079 float hi = e.fire_owner.hitsound_damage_dealt;
1080 float ty = e.fire_owner.typehitsound;
1081 Damage(e, e, e.fire_owner, d, e.fire_deathtype, DMG_NOWEP, e.origin, '0 0 0');
1082 if (e.fire_hitsound && e.fire_owner)
1083 {
1084 e.fire_owner.hitsound_damage_dealt = hi;
1085 e.fire_owner.typehitsound = ty;
1086 }
1087 e.fire_hitsound = true;
1088
1089 if (!IS_INDEPENDENT_PLAYER(e) && !STAT(FROZEN, e) && !StatusEffects_active(STATUSEFFECT_Frozen, e))
1090 IL_EACH(g_damagedbycontents, it.damagedbycontents && it != e,
1091 {
1092 if (!IS_DEAD(it) && it.takedamage && !IS_INDEPENDENT_PLAYER(it)
1093 && boxesoverlap(e.absmin - '1 1 1', e.absmax + '1 1 1', it.absmin, it.absmax))
1094 {
1095 t = autocvar_g_balance_firetransfer_time * (fireendtime - time);
1096 d = autocvar_g_balance_firetransfer_damage * e.fire_damagepersec * t;
1097 Fire_AddDamage(it, o, d, t, DEATH_FIRE.m_id);
1098 }
1099 });
1100}
#define IS_NOT_A_CLIENT(s)
Definition player.qh:243
float frametime
IntrusiveList g_damagedbycontents
Definition damage.qh:143
#define DMG_NOWEP
Definition damage.qh:104
#define IL_EACH(this, cond, body)

References Damage(), DMG_NOWEP, entity(), frametime, g_damagedbycontents, IL_EACH, IS_INDEPENDENT_PLAYER, IS_NOT_A_CLIENT, min(), STAT, StatusEffects_active(), StatusEffects_gettime(), and time.

◆ frag_centermessage_override()

bool frag_centermessage_override ( entity attacker,
entity targ,
int deathtype,
int kill_count_to_attacker,
int kill_count_to_target,
string attacker_name )

Definition at line 211 of file damage.qc.

212{
213 if (deathtype == DEATH_FIRE.m_id)
214 {
215 Send_Notification(NOTIF_ONE, attacker, MSG_CHOICE, CHOICE_FRAG_FIRE, targ.netname, kill_count_to_attacker, (IS_BOT_CLIENT(targ) ? -1 : CS(targ).ping));
216 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));
217 return true;
218 }
219
220 return MUTATOR_CALLHOOK(FragCenterMessage, attacker, targ, deathtype, kill_count_to_attacker, kill_count_to_target);
221}
float ping
Definition main.qh:169
void Send_Notification(NOTIF broadcast, entity client, MSG net_type, Notification net_name,...count)
Definition all.qc:1500
#define IS_BOT_CLIENT(v)
want: (IS_CLIENT(v) && !IS_REAL_CLIENT(v))
Definition utils.qh:15

References CS(), entity(), GetResource(), IS_BOT_CLIENT, MUTATOR_CALLHOOK, ping, RES_ARMOR, and Send_Notification().

Referenced by Obituary().

◆ GiveFrags()

void GiveFrags ( entity attacker,
entity targ,
float f,
int deathtype,
.entity weaponentity )

Definition at line 44 of file damage.qc.

45{
46 // TODO route through PlayerScores instead
47 if (game_stopped)
48 return;
49
50 if (f < 0)
51 {
52 if (targ == attacker) // suicide
53 GameRules_scoring_add(attacker, SUICIDES, 1);
54 else
55 {
56 // teamkill
57 int teamkills = GameRules_scoring_add(attacker, TEAMKILLS, 1);
59 f -= (teamkills * (teamkills - 1)) * 0.5; // negative 1, 2, 4, 7, 11, 16, 22, etc.
60 }
61 }
62 else
63 {
64 // regular frag
65 GameRules_scoring_add(attacker, KILLS, 1);
66 if (!warmup_stage && targ.playerid)
67 PlayerStats_GameReport_Event_Player(attacker, sprintf("kills-%d", targ.playerid), 1);
68 }
69
70 GameRules_scoring_add(targ, DEATHS, 1);
71
72 // FIXME fix the mess this is (we have REAL points now!)
73 if (MUTATOR_CALLHOOK(GiveFragsForKill, attacker, targ, f, deathtype, attacker.(weaponentity)))
74 f = M_ARGV(2, float);
75
76 attacker.totalfrags += f;
77
78 if (f)
79 GameRules_scoring_add_team(attacker, SCORE, f);
80}
bool autocvar_g_teamkill_punishing
Definition damage.qh:24
#define PlayerStats_GameReport_Event_Player(ent, eventid, val)
#define GameRules_scoring_add(client, fld, value)
Definition sv_rules.qh:85
#define GameRules_scoring_add_team(client, fld, value)
Definition sv_rules.qh:89

References autocvar_g_teamkill_punishing, entity(), game_stopped, GameRules_scoring_add, GameRules_scoring_add_team, M_ARGV, MUTATOR_CALLHOOK, PlayerStats_GameReport_Event_Player, and warmup_stage.

Referenced by MUTATOR_HOOKFUNCTION(), and Obituary().

◆ Heal()

bool Heal ( entity targ,
entity inflictor,
float amount,
float limit )

Definition at line 943 of file damage.qc.

944{
945 // TODO: mutator hook to control healing
946 if (game_stopped
947 || (IS_CLIENT(targ) && CS(targ).killcount == FRAGS_SPECTATOR)
948 || STAT(FROZEN, targ) || IS_DEAD(targ))
949 return false;
950
951 bool healed = (targ.event_heal)
952 ? targ.event_heal(targ, inflictor, amount, limit)
953 : false;
954 // TODO: additional handling? what if the healing kills them? should this abort if healing would do so etc
955 // TODO: healing fx!
956 // TODO: armor healing?
957 return healed;
958}

References CS(), entity(), FRAGS_SPECTATOR, game_stopped, IS_CLIENT, IS_DEAD, killcount, and STAT.

Referenced by bumblebee_pilot_frame(), M_Mage_Defend_Heal(), MUTATOR_HOOKFUNCTION(), trigger_heal_touch(), and W_Arc_Beam_Think().

◆ LogDeath()

void LogDeath ( string mode,
int deathtype,
entity killer,
entity killed )

Definition at line 101 of file damage.qc.

102{
104 return;
105 string s = strcat(":kill:", mode,
106 ":", ftos(killer.playerid),
107 ":", ftos(killed.playerid));
108 s = strcat(s, ":type=", Deathtype_Name(deathtype), ":items=");
109 s = AppendItemcodes(s, killer);
110 if (killed != killer)
111 {
112 s = strcat(s, ":victimitems=");
113 s = AppendItemcodes(s, killed);
114 }
115 GameLogEcho(s);
116}
string AppendItemcodes(string s, entity player)
Definition damage.qc:82
string Deathtype_Name(int deathtype)
Definition all.qc:3
void GameLogEcho(string s)
Definition gamelog.qc:15
bool autocvar_sv_eventlog
Definition gamelog.qh:3

References AppendItemcodes(), autocvar_sv_eventlog, Deathtype_Name(), entity(), ftos(), GameLogEcho(), and strcat().

Referenced by Obituary().

◆ Obituary()

void Obituary ( entity attacker,
entity inflictor,
entity targ,
int deathtype,
.entity weaponentity )

Definition at line 223 of file damage.qc.

224{
225 // Sanity check
226 if (!IS_PLAYER(targ))
227 {
228 backtrace("Obituary called on non-player?!\n");
229 return;
230 }
231
232 // Declarations
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;
237
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) : "");
241
242 // Abort now if a mutator requests it
243 if (MUTATOR_CALLHOOK(ClientObituary, inflictor, attacker, targ, deathtype, attacker.(weaponentity)))
244 {
245 CS(targ).killcount = 0;
246 return;
247 }
248 notif_anonymous = M_ARGV(5, bool);
249
250 // TODO: Replace "???" with a translatable "Anonymous player" string
251 // https://gitlab.com/xonotic/xonotic-data.pk3dir/-/issues/2839
252 if (notif_anonymous)
253 attacker_name = "???";
254
255 #ifdef NOTIFICATIONS_DEBUG
256 Debug_Notification(sprintf(
257 "Obituary(%s, %s, %s, %s = %d);\n",
258 attacker_name,
259 inflictor.netname,
260 targ.netname,
261 Deathtype_Name(deathtype),
262 deathtype
263 ));
264 #endif
265
266 // =======
267 // SUICIDE
268 // =======
269 if (targ == attacker)
270 {
271 if (DEATH_ISSPECIAL(deathtype))
272 {
273 if (deathtype == DEATH_TEAMCHANGE.m_id || deathtype == DEATH_AUTOTEAMCHANGE.m_id)
274 Obituary_SpecialDeath(targ, false, false, deathtype, targ.netname, deathlocation, "", "", targ.team, 0, 0);
275 else
276 {
277 switch (DEATH_ENT(deathtype))
278 {
279 case DEATH_MIRRORDAMAGE:
280 Obituary_SpecialDeath(targ, false, false, deathtype, targ.netname, deathlocation, "", "", CS(targ).killcount, 0, 0);
281 break;
282 case DEATH_HURTTRIGGER:
283 {
284 bool msg_from_ent = (inflictor && inflictor.message != "");
285 Obituary_SpecialDeath(targ, false, msg_from_ent, deathtype,
286 targ.netname,
287 (msg_from_ent ? inflictor.message : deathlocation),
288 (msg_from_ent ? deathlocation : ""),
289 "", CS(targ).killcount, 0, 0
290 );
291 break;
292 }
293 default:
294 Obituary_SpecialDeath(targ, false, false, deathtype, targ.netname, deathlocation, "", "", CS(targ).killcount, 0, 0);
295 break;
296 }
297 }
298 }
299 else if (!Obituary_WeaponDeath(targ, false, deathtype, targ.netname, deathlocation, "", CS(targ).killcount, 0))
300 {
301 backtrace("SUICIDE: what the hell happened here?\n");
302 return;
303 }
304 LogDeath("suicide", deathtype, targ, targ);
305 if (deathtype != DEATH_AUTOTEAMCHANGE.m_id) // special case: don't negate frags if auto switched
306 GiveFrags(attacker, targ, -1, deathtype, weaponentity);
307 }
308
309 // ======
310 // MURDER
311 // ======
312 else if (IS_PLAYER(attacker))
313 {
314 if (SAME_TEAM(attacker, targ))
315 {
316 LogDeath("tk", deathtype, attacker, targ);
317 GiveFrags(attacker, targ, -1, deathtype, weaponentity);
318
319 CS(attacker).killcount = 0;
320
321 Send_Notification(NOTIF_ONE, attacker, MSG_CENTER, CENTER_DEATH_TEAMKILL_FRAG, targ.netname);
322 Send_Notification(NOTIF_ONE, targ, MSG_CENTER, CENTER_DEATH_TEAMKILL_FRAGGED, attacker_name);
323 Send_Notification(NOTIF_ALL, NULL, MSG_INFO, APP_TEAM_NUM(targ.team, INFO_DEATH_TEAMKILL), targ.netname, attacker_name, deathlocation, CS(targ).killcount);
324
325 // In this case, the death message will ALWAYS be "foo was betrayed by bar"
326 // No need for specific death/weapon messages...
327 }
328 else
329 {
330 LogDeath("frag", deathtype, attacker, targ);
331 GiveFrags(attacker, targ, 1, deathtype, weaponentity);
332
333 CS(attacker).taunt_soundtime = time + 1;
334 ++CS(attacker).killcount;
335
336 ++attacker.killsound;
337
338 // TODO: improve SPREE_ITEM and KILL_SPREE_LIST
339 // these 2 macros are spread over multiple files
340 #define SPREE_ITEM(counta,countb,center,normal,gentle) \
341 case counta: \
342 Send_Notification(NOTIF_ONE, attacker, MSG_ANNCE, ANNCE_KILLSTREAK_##countb); \
343 if (!warmup_stage) \
344 PlayerStats_GameReport_Event_Player(attacker, PLAYERSTATS_ACHIEVEMENT_KILL_SPREE_##counta, 1); \
345 break;
346
347 switch (CS(attacker).killcount)
348 {
350 default:
351 break;
352 }
353 #undef SPREE_ITEM
354
356 {
358 notif_firstblood = true; // modify the current messages so that they too show firstblood information
361
362 // tell spree_inf and spree_cen that this is a first-blood and first-victim event
363 kill_count_to_attacker = -1;
364 kill_count_to_target = -2;
365 }
366 else
367 {
368 kill_count_to_attacker = CS(attacker).killcount;
369 kill_count_to_target = 0;
370 }
371
372 if (targ.istypefrag)
373 {
375 NOTIF_ONE,
376 attacker,
377 MSG_CHOICE,
378 CHOICE_TYPEFRAG,
379 targ.netname,
380 kill_count_to_attacker,
381 (IS_BOT_CLIENT(targ) ? -1 : CS(targ).ping)
382 );
384 NOTIF_ONE,
385 targ,
386 MSG_CHOICE,
387 CHOICE_TYPEFRAGGED,
388 attacker_name,
389 kill_count_to_target,
390 GetResource(attacker, RES_HEALTH),
391 GetResource(attacker, RES_ARMOR),
392 (IS_BOT_CLIENT(attacker) ? -1 : CS(attacker).ping)
393 );
394 }
395 else if (!frag_centermessage_override(attacker, targ, deathtype, kill_count_to_attacker, kill_count_to_target, attacker_name))
396 {
398 NOTIF_ONE,
399 attacker,
400 MSG_CHOICE,
401 CHOICE_FRAG,
402 targ.netname,
403 kill_count_to_attacker,
404 (IS_BOT_CLIENT(targ) ? -1 : CS(targ).ping)
405 );
407 NOTIF_ONE,
408 targ,
409 MSG_CHOICE,
410 CHOICE_FRAGGED,
411 attacker_name,
412 kill_count_to_target,
413 GetResource(attacker, RES_HEALTH),
414 GetResource(attacker, RES_ARMOR),
415 (IS_BOT_CLIENT(attacker) ? -1 : CS(attacker).ping)
416 );
417 }
418
419 if (!Obituary_WeaponDeath(targ, true, deathtype, targ.netname, attacker_name, deathlocation, CS(targ).killcount, kill_count_to_attacker))
420 {
421 bool msg_from_ent = (DEATH_ENT(deathtype) == DEATH_HURTTRIGGER && inflictor && inflictor.message2 != "");
422 Obituary_SpecialDeath(targ, true, msg_from_ent, deathtype,
423 targ.netname,
424 attacker_name,
425 (msg_from_ent ? inflictor.message2 : deathlocation),
426 (msg_from_ent ? deathlocation : ""),
427 CS(targ).killcount, kill_count_to_attacker, 0
428 );
429 }
430 }
431 }
432
433 // =============
434 // ACCIDENT/TRAP
435 // =============
436 else
437 {
438 switch (DEATH_ENT(deathtype))
439 {
440 // For now, we're just forcing HURTTRIGGER to behave as "DEATH_VOID" and giving it no special options...
441 // Later on you will only be able to make custom messages using DEATH_CUSTOM,
442 // and there will be a REAL DEATH_VOID implementation which mappers will use.
443 case DEATH_HURTTRIGGER:
444 {
445 bool msg_from_ent = (inflictor && inflictor.message != "");
446 Obituary_SpecialDeath(targ, false, msg_from_ent, deathtype,
447 targ.netname,
448 (msg_from_ent ? inflictor.message : deathlocation),
449 (msg_from_ent ? deathlocation : ""),
450 "",
451 CS(targ).killcount, 0, 0
452 );
453 break;
454 }
455 case DEATH_CUSTOM:
456 Obituary_SpecialDeath(targ, false, false, deathtype,
457 targ.netname,
458 ((strstrofs(deathmessage, "%", 0) < 0) ? strcat("%s ", deathmessage) : deathmessage),
459 deathlocation,
460 "",
461 CS(targ).killcount, 0, 0
462 );
463 break;
464 default:
465 Obituary_SpecialDeath(targ, false, false, deathtype, targ.netname, deathlocation, "", "", CS(targ).killcount, 0, 0);
466 break;
467 }
468
469 LogDeath("accident", deathtype, targ, targ);
470 GiveFrags(targ, targ, -1, deathtype, weaponentity);
471
472 if (GameRules_scoring_add(targ, SCORE, 0) == -5)
473 {
474 Send_Notification(NOTIF_ONE, targ, MSG_ANNCE, ANNCE_ACHIEVEMENT_BOTLIKE);
475 if (!warmup_stage)
477 }
478 }
479
480 // reset target kill count
481 CS(targ).killcount = 0;
482}
void GiveFrags(entity attacker, entity targ, float f, int deathtype,.entity weaponentity)
Definition damage.qc:44
float Obituary_WeaponDeath(entity notif_target, bool murder, int deathtype, string s1, string s2, string s3, float f1, float f2)
Definition damage.qc:166
bool frag_centermessage_override(entity attacker, entity targ, int deathtype, int kill_count_to_attacker, int kill_count_to_target, string attacker_name)
Definition damage.qc:211
void Obituary_SpecialDeath(entity notif_target, bool murder, bool msg_from_ent, int deathtype, string s1, string s2, string s3, string s4, float f1, float f2, float f3)
Definition damage.qc:118
void LogDeath(string mode, int deathtype, entity killer, entity killed)
Definition damage.qc:101
string deathmessage
Definition damage.qh:73
float checkrules_firstblood
Definition damage.qh:43
#define DEATH_ENT(t)
Definition all.qh:43
#define strstrofs
#define backtrace(msg)
Definition log.qh:96
#define KILL_SPREE_LIST
Definition all.qh:486
bool autocvar_notification_server_allows_location
Definition all.qh:308
#define APP_TEAM_NUM(num, prefix)
Definition all.qh:88
const string PLAYERSTATS_ACHIEVEMENT_FIRSTBLOOD
const string PLAYERSTATS_ACHIEVEMENT_FIRSTVICTIM
const string PLAYERSTATS_ACHIEVEMENT_BOTLIKE
#define NULL
Definition post.qh:14
string NearestLocation(vector p)
Definition chat.qc:446

References APP_TEAM_NUM, autocvar_notification_server_allows_location, backtrace, checkrules_firstblood, CS(), DEATH_ENT, DEATH_ISSPECIAL, deathmessage, Deathtype_Name(), entity(), frag_centermessage_override(), GameRules_scoring_add, GetResource(), GiveFrags(), IS_BOT_CLIENT, IS_PLAYER, KILL_SPREE_LIST, killcount, LogDeath(), M_ARGV, MUTATOR_CALLHOOK, NearestLocation(), NULL, Obituary_SpecialDeath(), Obituary_WeaponDeath(), ping, PLAYERSTATS_ACHIEVEMENT_BOTLIKE, PLAYERSTATS_ACHIEVEMENT_FIRSTBLOOD, PLAYERSTATS_ACHIEVEMENT_FIRSTVICTIM, PlayerStats_GameReport_Event_Player, RES_ARMOR, SAME_TEAM, Send_Notification(), strcat(), strstrofs, time, and warmup_stage.

Referenced by PlayerDamage().

◆ Obituary_SpecialDeath()

void Obituary_SpecialDeath ( entity notif_target,
bool murder,
bool msg_from_ent,
int deathtype,
string s1,
string s2,
string s3,
string s4,
float f1,
float f2,
float f3 )

Definition at line 118 of file damage.qc.

125{
126 if (!DEATH_ISSPECIAL(deathtype))
127 {
128 backtrace("Obituary_SpecialDeath called without a special deathtype?\n");
129 return;
130 }
131
132 entity deathent = REGISTRY_GET(Deathtypes, deathtype - DT_FIRST);
133 if (!deathent)
134 {
135 backtrace("Obituary_SpecialDeath: Could not find deathtype entity!\n");
136 return;
137 }
138
139 if ((g_cts || notif_target.killindicator_teamchange == -2) && deathtype == DEATH_KILL.m_id)
140 return; // TODO: somehow put this in CTS gametype file!
141
142 Notification death_message = (msg_from_ent)
143 ? (murder ? deathent.death_msg_ent_murder : deathent.death_msg_ent_self)
144 : (murder ? deathent.death_msgmurder : deathent.death_msgself);
145 if (death_message)
146 {
148 NOTIF_ONE,
149 notif_target,
150 MSG_MULTI,
151 death_message,
152 s1, s2, s3, s4,
153 f1, f2, f3, 0
154 );
156 NOTIF_ALL_EXCEPT,
157 notif_target,
158 MSG_INFO,
159 death_message.nent_msginfo,
160 s1, s2, s3, s4,
161 f1, f2, f3, 0
162 );
163 }
164}
#define g_cts
Definition cts.qh:36
const int DT_FIRST
Definition all.qh:39
spree_inf s1 s2 s3loc s2 s1
Definition all.inc:285
spree_inf s1 s2 s3loc s2 spree_inf s1 s2 s3loc s2 spree_inf s1 s2 s3loc s2 s1 s2loc s1 s2loc s1 s2loc s1 s2loc s1 s2loc s1 s2loc s1 s2 f1points f2
Definition all.inc:370
spree_inf s1 s2 s3loc s2 spree_inf s1 s2 s3loc s2 spree_inf s1 s2 s3loc s2 s1 s2loc s1 s2loc s1 s2loc s1 s2loc s1 s2loc s1 s2loc s1 s2 f1points s1 s2
Definition all.inc:477
f1
Definition all.inc:569
void Send_Notification_Core(NOTIF broadcast, entity client, MSG net_type, Notification net_name, string s1, string s2, string s3, string s4, float f1, float f2, float f3, float f4)
Definition all.qc:1646
entity Notification
always last
Definition all.qh:85
#define REGISTRY_GET(id, i)
Definition registry.qh:62

References backtrace, DEATH_ISSPECIAL, DT_FIRST, entity(), f1, f2, g_cts, REGISTRY_GET, s1, s2, and Send_Notification_Core().

Referenced by Obituary().

◆ Obituary_WeaponDeath()

float Obituary_WeaponDeath ( entity notif_target,
bool murder,
int deathtype,
string s1,
string s2,
string s3,
float f1,
float f2 )

Definition at line 166 of file damage.qc.

172{
173 Weapon death_weapon = DEATH_WEAPONOF(deathtype);
174 if (death_weapon == WEP_Null)
175 return false;
176
177 w_deathtype = deathtype;
178 Notification death_message = (murder ? death_weapon.wr_killmessage(death_weapon) : death_weapon.wr_suicidemessage(death_weapon));
179 w_deathtype = false;
180
181 if (death_message)
182 {
184 NOTIF_ONE,
185 notif_target,
186 MSG_MULTI,
187 death_message,
188 s1, s2, s3, "",
189 f1, f2, 0, 0
190 );
191 // send the info part to everyone
193 NOTIF_ALL_EXCEPT,
194 notif_target,
195 MSG_INFO,
196 death_message.nent_msginfo,
197 s1, s2, s3, "",
198 f1, f2, 0, 0
199 );
200 }
201 else
203 "Obituary_WeaponDeath(): ^1Deathtype ^7(%d)^1 has no notification for weapon %s!\n",
204 deathtype,
205 death_weapon.netname
206 );
207
208 return true;
209}
fields which are explicitly/manually set are marked with "M", fields set automatically are marked wit...
Definition weapon.qh:42
virtual void wr_suicidemessage()
(SERVER) notification number for suicide message (may inspect w_deathtype for details)
Definition weapon.qh:118
string netname
M: refname : reference name name.
Definition weapon.qh:84
virtual void wr_killmessage()
(SERVER) notification number for kill message (may inspect w_deathtype for details)
Definition weapon.qh:123
int w_deathtype
#define LOG_TRACEF(...)
Definition log.qh:75

References DEATH_WEAPONOF, entity(), f1, f2, LOG_TRACEF, Weapon::netname, s1, s2, Send_Notification_Core(), w_deathtype, Weapon::wr_killmessage(), and Weapon::wr_suicidemessage().

Referenced by Obituary().

◆ RadiusDamage()

float RadiusDamage ( entity inflictor,
entity attacker,
float coredamage,
float edgedamage,
float rad,
entity cantbe,
entity mustbe,
float forceintensity,
int deathtype,
.entity weaponentity,
entity directhitentity )

Definition at line 929 of file damage.qc.

934{
936 inflictor, (inflictor.origin + (inflictor.mins + inflictor.maxs) * 0.5), inflictor.velocity, attacker,
937 coredamage, edgedamage, rad, cantbe, mustbe,
938 false, forceintensity, '1 1 1',
939 deathtype, weaponentity, directhitentity
940 );
941}
float RadiusDamageForSource(entity inflictor, vector inflictororigin, vector inflictorvelocity, entity attacker, float coredamage, float edgedamage, float rad, entity cantbe, entity mustbe, bool inflictorselfdamage, float forceintensity, vector forcexyzscale, int deathtype,.entity weaponentity, entity directhitentity)
Definition damage.qc:704

References entity(), and RadiusDamageForSource().

Referenced by bumblebee_blowup(), CheatImpulse(), func_breakable_destroy(), M_Golem_Attack_Lightning_Explode(), M_Golem_Attack_Smash(), M_Mage_Attack_Push(), M_Mage_Attack_Spike_Explode(), M_Spider_Attack_Web_Explode(), M_Wyvern_Attack_Fireball_Explode(), nade_normal_boom(), PlayerTouchExplode(), racer_blowup(), raptor_blowup(), raptor_bomblet_boom(), spiderbot_blowup(), turret_flac_projectile_think_explode(), turret_projectile_explode(), vehicles_projectile_explode(), W_Arc_Bolt_Explode(), W_Arc_Bolt_Touch(), W_Crylink_LinkExplode(), W_Crylink_LinkJoinEffect_Think(), W_Crylink_Touch(), W_Devastator_DoRemoteExplode(), W_Electro_Explode(), W_Electro_ExplodeCombo(), W_Electro_ExplodeComboThink(), W_Fireball_Explode(), W_Hagar_Explode(), W_Hagar_Explode2(), W_HLAC_Touch(), W_Hook_ExplodeThink(), W_MineLayer_DoRemoteExplode(), W_MineLayer_Explode(), W_Mortar_Grenade_Explode(), W_Mortar_Grenade_Explode2(), W_OverkillRocketPropelledChainsaw_Explode(), W_RocketMinsta_Explosion(), W_RocketMinsta_Laser_Damage(), W_Seeker_Flac_Explode(), W_Seeker_Missile_Explode(), W_Tuba_NoteOn(), and walker_rocket_explode().

◆ RadiusDamageForSource()

float RadiusDamageForSource ( entity inflictor,
vector inflictororigin,
vector inflictorvelocity,
entity attacker,
float coredamage,
float edgedamage,
float rad,
entity cantbe,
entity mustbe,
bool inflictorselfdamage,
float forceintensity,
vector forcexyzscale,
int deathtype,
.entity weaponentity,
entity directhitentity )

Definition at line 704 of file damage.qc.

709{
711 {
712 backtrace("RadiusDamage called recursively! Expect stuff to go HORRIBLY wrong.");
713 return 0;
714 }
715
716 if (rad < 0)
717 rad = 0;
718
720
721 float tfloordmg = autocvar_g_throughfloor_damage;
722 float tfloorforce = autocvar_g_throughfloor_force;
723
724 float stat_damagedone = 0;
725 float total_damage_to_creatures = 0;
726
727 vector force;
728 if (!(deathtype & (HITTYPE_SOUND | HITTYPE_SPAM))) // do not send bandwidth-hogging radial spam attacks
729 {
730 force = inflictorvelocity;
731 if (force == '0 0 0')
732 force = '0 0 -1';
733 else
734 force = normalize(force);
735 if (forceintensity >= 0)
736 Damage_DamageInfo(inflictororigin, coredamage, edgedamage, rad, forceintensity * force, deathtype, 0, attacker);
737 else
738 Damage_DamageInfo(inflictororigin, coredamage, edgedamage, -rad, (-forceintensity) * force, deathtype, 0, attacker);
739 }
740
741 entity targ = WarpZone_FindRadius(inflictororigin, rad + MAX_DAMAGEEXTRARADIUS, false);
742 for (; targ; targ = targ.chain)
743 if ((targ != inflictor || inflictorselfdamage)
744 && (((cantbe != targ) && !mustbe) || mustbe == targ)
745 && targ.takedamage)
746 {
747 // calculate distance from nearest point on target to nearest point on inflictor
748 // instead of origin to ensure full damage on impacts
749
750 vector nearest = targ.WarpZone_findradius_nearest;
751
752 // optimize code by getting inflictororigin_wz from WarpZone_FindRadius calculations instead of
753 //vector inflictororigin_wz = WarpZone_TransformOrigin(targ, inflictororigin);
754
755 vector inflictororigin_wz = targ.WarpZone_findradius_nearest + targ.WarpZone_findradius_dist;
756 vector inflictornearest = NearestPointOnBoundingBox(
757 inflictororigin_wz + inflictor.mins, inflictororigin_wz + inflictor.maxs, nearest);
758 vector diff = inflictornearest - nearest;
759
760 // round up a little on the damage to ensure full damage on impacts
761 // and turn the distance into a fraction of the radius
762 float dist = max(0, vlen(diff) - bound(MIN_DAMAGEEXTRARADIUS, targ.damageextraradius, MAX_DAMAGEEXTRARADIUS));
763 if (dist <= rad)
764 {
765 float f = (rad > 0) ? 1 - (dist / rad) : 1;
766 // at this point f can't be < 0 or > 1
767 float finaldmg = coredamage * f + edgedamage * (1 - f);
768 if (finaldmg > 0)
769 {
770 vector hitloc;
771 float a, c;
772
773 // if it's a player, use the view origin as reference
774 vector center = CENTER_OR_VIEWOFS(targ);
775
777 {
778 if (targ != attacker) // always use target's bbox centerpoint
779 center = targ.origin + ((targ.mins + targ.maxs) * 0.5);
780 else // targ == attacker
781 {
782 #if 0
783 // code stolen from W_SetupShot_Dir_ProjectileSize_Range()
784 vector md = targ.(weaponentity).movedir;
785 vector dv = v_right * -md.y + v_up * md.z;
786 vector mi = '0 0 0', ma = '0 0 0';
787
788 if (IS_CLIENT(targ)) // no antilag for non-clients!
789 {
790 if (CS(targ).antilag_debug)
791 tracebox_antilag(targ, center, mi, ma, center + dv, MOVE_NORMAL, targ, CS(targ).antilag_debug);
792 else
793 tracebox_antilag(targ, center, mi, ma, center + dv, MOVE_NORMAL, targ, ANTILAG_LATENCY(targ));
794 }
795 else
796 tracebox(center, mi, ma, center + dv, MOVE_NORMAL, targ);
797
798 center.z = trace_endpos.z;
799 #else
800 // very cheap way but it skips move into solid checks which is fine most of the time for now AFAIK
801 // this should only really be an issue with some rare edge cases where
802 // shot origin was prevented from going into a ceiling but it still explodes at the ceiling
803 // shot origin wasn't raised as high as possible and the shooter gets upwards knockback
804 // TL;DR: no bugs if vertical shot origin is always within player bbox
805 center.z += targ.(weaponentity).movedir.z;
806 #endif
807 }
808 }
809
810 /* debug prints
811 print(sprintf("origin vec %v\n", targ.origin));
812 print(sprintf("movedir vec %v\n", targ.(weaponentity).movedir));
813 print(sprintf("old def vec %v\n", CENTER_OR_VIEWOFS(targ)));
814 print(sprintf("origin+vofs %v\n", targ.origin + targ.view_ofs));
815 print(sprintf("bbox center %v\n", (targ.origin + ((targ.mins + targ.maxs) * 0.5))));
816 print(sprintf("center vec %v\n", center));
817 print(sprintf("shotorg vec %v\n", w_shotorg));
818 print("\n");
819 */
820
821 force = normalize(center - inflictororigin_wz);
822 force *= (finaldmg / max(coredamage, edgedamage)) * forceintensity;
823 hitloc = nearest;
824
825 // apply special force scalings
826 if (forcexyzscale.x)
827 force.x *= forcexyzscale.x;
828 if (forcexyzscale.y)
829 force.y *= forcexyzscale.y;
830 if (forcexyzscale.z)
831 force.z *= forcexyzscale.z;
832
833 if (targ != directhitentity)
834 {
835 float hits;
836 float total;
837 float hitratio;
838 float mininv_f, mininv_d;
839
840 // test line of sight to multiple positions on box,
841 // and do damage if any of them hit
842 hits = 0;
843
844 // we know: max stddev of hitratio = 1 / (2 * sqrt(n))
845 // so for a given max stddev:
846 // n = (1 / (2 * max stddev of hitratio))^2
847
848 mininv_d = (finaldmg * (1-tfloordmg)) / autocvar_g_throughfloor_damage_max_stddev;
849 mininv_f = (vlen(force) * (1-tfloorforce)) / autocvar_g_throughfloor_force_max_stddev;
850
852 LOG_INFOF("THROUGHFLOOR: D=%f F=%f max(dD)=1/%f max(dF)=1/%f", finaldmg, vlen(force), mininv_d, mininv_f);
853
854 total = 0.25 * (max(mininv_f, mininv_d) ** 2);
855
857 LOG_INFOF(" steps=%f", total);
858
859 if (IS_PLAYER(targ))
861 else
863
865 LOG_INFOF(" steps=%f dD=%f dF=%f", total, finaldmg * (1-tfloordmg) / (2 * sqrt(total)), vlen(force) * (1-tfloorforce) / (2 * sqrt(total)));
866
867 for (c = 0; c < total; ++c)
868 {
869 //traceline(targ.WarpZone_findradius_findorigin, nearest, MOVE_NOMONSTERS, inflictor);
870 WarpZone_TraceLine(inflictororigin, WarpZone_UnTransformOrigin(targ, nearest), MOVE_NOMONSTERS, inflictor);
871 if (trace_fraction == 1 || trace_ent == targ)
872 {
873 ++hits;
874 if (hits > 1)
875 hitloc += nearest;
876 else
877 hitloc = nearest;
878 }
879 nearest.x = targ.origin.x + targ.mins.x + random() * targ.size.x;
880 nearest.y = targ.origin.y + targ.mins.y + random() * targ.size.y;
881 nearest.z = targ.origin.z + targ.mins.z + random() * targ.size.z;
882 }
883
884 nearest = hitloc * (1 / max(1, hits));
885 hitratio = hits / total;
886 a = bound(0, tfloordmg + (1 - tfloordmg) * hitratio, 1);
887 finaldmg *= a;
888 a = bound(0, tfloorforce + (1 - tfloorforce) * hitratio, 1);
889 force *= a;
890
892 LOG_INFOF(" D=%f F=%f", finaldmg, vlen(force));
893
894 /*if (targ == attacker)
895 {
896 print("hits ", ftos(hits), " / ", ftos(total));
897 print(" finaldmg ", ftos(finaldmg), " force ", ftos(vlen(force)));
898 print(" (", vtos(force), ") (", ftos(a), ")\n");
899 }*/
900 }
901
902 if (finaldmg || force)
903 {
904 if (targ.iscreature)
905 {
906 total_damage_to_creatures += finaldmg;
907
908 if (accuracy_isgooddamage(attacker, targ))
909 stat_damagedone += finaldmg;
910 }
911
912 if (targ == directhitentity || DEATH_ISSPECIAL(deathtype))
913 Damage(targ, inflictor, attacker, finaldmg, deathtype, weaponentity, nearest, force);
914 else
915 Damage(targ, inflictor, attacker, finaldmg, deathtype | HITTYPE_SPLASH, weaponentity, nearest, force);
916 }
917 }
918 }
919 }
920
921 RadiusDamage_running = false;
922
923 if (!DEATH_ISSPECIAL(deathtype))
924 accuracy_add(attacker, DEATH_WEAPONOF(deathtype), 0, min(max(coredamage, edgedamage), stat_damagedone), 0); // add to hit
925
926 return total_damage_to_creatures;
927}
void tracebox_antilag(entity source, vector v1, vector mi, vector ma, vector v2, float nomonst, entity forent, float lag)
Definition antilag.qc:210
float antilag_debug
Definition antilag.qc:19
#define ANTILAG_LATENCY(e)
Definition antilag.qh:19
vector v_up
const float MOVE_NOMONSTERS
entity trace_ent
const float MOVE_NORMAL
vector v_right
vector trace_endpos
float trace_fraction
bool RadiusDamage_running
Definition damage.qh:111
int autocvar_g_player_damageplayercenter
Definition damage.qh:106
float autocvar_g_throughfloor_force_max_stddev
Definition damage.qh:9
float autocvar_g_throughfloor_damage_max_stddev
Definition damage.qh:8
float autocvar_g_throughfloor_force
Definition damage.qh:7
bool autocvar_g_throughfloor_debug
Definition damage.qh:5
float autocvar_g_throughfloor_min_steps_other
Definition damage.qh:12
float autocvar_g_throughfloor_min_steps_player
Definition damage.qh:10
float autocvar_g_throughfloor_damage
Definition damage.qh:6
float autocvar_g_throughfloor_max_steps_other
Definition damage.qh:13
float autocvar_g_throughfloor_max_steps_player
Definition damage.qh:11
void Damage_DamageInfo(vector org, float coredamage, float edgedamage, float rad, vector force, int deathtype, float bloodtype, entity dmgowner)
const float MAX_DAMAGEEXTRARADIUS
const float MIN_DAMAGEEXTRARADIUS
const int HITTYPE_SPLASH
automatically set by RadiusDamage
Definition all.qh:32
entity WarpZone_FindRadius(vector org, float rad, bool needlineofsight)
Definition common.qc:651
void WarpZone_TraceLine(vector org, vector end, float nomonsters, entity forent)
Definition common.qc:328
vector WarpZone_UnTransformOrigin(entity wz, vector v)
Definition common.qc:519
#define LOG_INFOF(...)
Definition log.qh:63
vector movedir
Definition viewloc.qh:18
float ceil(float f)
float bound(float min, float value, float max)
float random(void)
float sqrt(float f)
#define CENTER_OR_VIEWOFS(ent)
Definition utils.qh:31
ERASEABLE vector NearestPointOnBoundingBox(vector mi, vector ma, vector org)
Definition vector.qh:200

References accuracy_add(), accuracy_isgooddamage(), antilag_debug, ANTILAG_LATENCY, autocvar_g_player_damageplayercenter, autocvar_g_throughfloor_damage, autocvar_g_throughfloor_damage_max_stddev, autocvar_g_throughfloor_debug, autocvar_g_throughfloor_force, autocvar_g_throughfloor_force_max_stddev, autocvar_g_throughfloor_max_steps_other, autocvar_g_throughfloor_max_steps_player, autocvar_g_throughfloor_min_steps_other, autocvar_g_throughfloor_min_steps_player, backtrace, bound(), ceil(), CENTER_OR_VIEWOFS, CS(), Damage(), Damage_DamageInfo(), DEATH_ISSPECIAL, DEATH_WEAPONOF, entity(), HITTYPE_SOUND, HITTYPE_SPAM, HITTYPE_SPLASH, IS_CLIENT, IS_PLAYER, LOG_INFOF, max(), MAX_DAMAGEEXTRARADIUS, min(), MIN_DAMAGEEXTRARADIUS, MOVE_NOMONSTERS, MOVE_NORMAL, movedir, NearestPointOnBoundingBox(), normalize(), RadiusDamage_running, random(), sqrt(), trace_endpos, trace_ent, trace_fraction, tracebox_antilag(), v_right, v_up, vector, vlen(), WarpZone_FindRadius(), WarpZone_TraceLine(), and WarpZone_UnTransformOrigin().

Referenced by RadiusDamage(), W_Blaster_Touch(), and W_Devastator_Explode().