X-Git-Url: https://de.git.xonotic.org/?a=blobdiff_plain;f=qcsrc%2Fclient%2Fdamage.qc;h=fc8b83b5b6c8666a11efe0961ebe8af011cfcbfb;hb=f2756ec48ef7d5c2edebc9bd4885cc753060b2e6;hp=635b467a0569e751ddb46c4ac7238973927d3c8b;hpb=53786a209467e5da2df529454e6cb70dc14e1fd6;p=xonotic%2Fxonotic-data.pk3dir.git diff --git a/qcsrc/client/damage.qc b/qcsrc/client/damage.qc index 635b467a0..fc8b83b5b 100644 --- a/qcsrc/client/damage.qc +++ b/qcsrc/client/damage.qc @@ -1,7 +1,119 @@ -void DamageEffect(float dmg, float type, float specnum1, float entnumber); +void DamageEffect_Think() +{ + // if particle distribution is enabled, slow ticrate by total number of damages + if(autocvar_cl_damageeffect_distribute) + self.nextthink = time + autocvar_cl_damageeffect_ticrate * self.owner.total_damages; + else + self.nextthink = time + autocvar_cl_damageeffect_ticrate; + + if(time >= self.cnt || !self.owner || !self.owner.modelindex || !self.owner.drawmask) + { + // time is up or the player got gibbed / disconnected + self.owner.total_damages = max(0, self.owner.total_damages - 1); + remove(self); + return; + } + if(self.state && !self.owner.csqcmodel_isdead) + { + // if the player was dead but is now alive, it means he respawned + // if so, clear his damage effects, or damages from his dead body will be copied back + self.owner.total_damages = max(0, self.owner.total_damages - 1); + remove(self); + return; + } + self.state = self.owner.csqcmodel_isdead; +#ifdef COMPAT_XON050_ENGINE + if(self.owner.isplayermodel && (self.owner.entnum == player_localentnum || self.owner.entnum == spectatee_status) && !autocvar_chase_active) +#else + if(self.owner.isplayermodel && (self.owner.entnum == player_localentnum) && !autocvar_chase_active) +#endif + return; // if we aren't using a third person camera, hide our own effects + + // now generate the particles + vector org; + org = gettaginfo(self, 0); // origin at attached location + pointparticles(self.team, org, '0 0 0', 1); +} + +void DamageEffect(vector hitorg, float dmg, float type, float specnum) +{ + // particle effects for players and objects damaged by weapons (eg: flames coming out of victims shot with rockets) + + float life, nearestbone; + string specstr, effectname; + entity e; + + if(!autocvar_cl_damageeffect || autocvar_cl_gentle || autocvar_cl_gentle_damage) + return; + if(!self || !self.modelindex || !self.drawmask) + return; + + // if this is a rigged mesh, the effect will show on the bone where damage was dealt + // we do this by choosing the skeletal bone closest to the impact, and attaching our entity to it + // if there's no skeleton, object origin will automatically be selected + FOR_EACH_TAG(self) + { + if(!tagnum) + continue; // skip empty bones + // blacklist bones positioned outside the mesh, or the effect will be floating + // TODO: Do we have to do it this way? Why do these bones exist at all? + if(gettaginfo_name == "master" || gettaginfo_name == "knee_L" || gettaginfo_name == "knee_R" || gettaginfo_name == "leg_L" || gettaginfo_name == "leg_R") + continue; // player model bone blacklist + + // now choose the bone closest to impact origin + if(vlen(hitorg - gettaginfo(self, tagnum)) <= vlen(hitorg - gettaginfo(self, nearestbone))) + nearestbone = tagnum; + } + gettaginfo(self, nearestbone); // set gettaginfo_name + + // return if we reached our damage effect limit or damages are disabled + // TODO: When the limit is reached, it would be better if the oldest damage was removed instead of not adding a new one + if(nearestbone) + { + if(self.total_damages >= autocvar_cl_damageeffect_bones) + return; // allow multiple damages on skeletal models + } + else + { + if(autocvar_cl_damageeffect < 2 || self.total_damages) + return; // allow a single damage on non-skeletal models + } + + life = bound(autocvar_cl_damageeffect_lifetime_min, dmg * autocvar_cl_damageeffect_lifetime, autocvar_cl_damageeffect_lifetime_max); + specstr = species_prefix(specnum); + type = DEATH_WEAPONOF(type); + e = get_weaponinfo(type); + + effectname = strcat("damage_", e.netname); + + // if damage was dealt with a bullet weapon, our effect is blood + // since blood is species dependent, include the species tag + if(type == WEP_SHOTGUN || type == WEP_UZI || type == WEP_RIFLE) + { + if(self.isplayermodel) + { + effectname = strcat(effectname, "_", specstr); + effectname = substring(effectname, 0, strlen(effectname) - 1); // remove the _ symbol at the end of the species tag + } + else + return; // objects don't bleed + } + + e = spawn(); + setmodel(e, "null"); // necessary to attach and read origin // samual: FIXME: this is weird, is there some better way to do this? + setattachment(e, self, gettaginfo_name); // attach to the given bone + e.classname = "damage"; + e.owner = self; + e.cnt = time + life; + e.team = particleeffectnum(effectname); + e.think = DamageEffect_Think; + e.nextthink = time; + self.total_damages += 1; +} + void Ent_DamageInfo(float isNew) { - float dmg, rad, edge, thisdmg, forcemul, species; + float dmg, rad, edge, thisdmg, forcemul, species, hitplayer; vector force, thisforce; entity oldself; @@ -32,13 +144,20 @@ void Ent_DamageInfo(float isNew) else forcemul = 1; - for(self = findradius(w_org, rad); self; self = self.chain) + for(self = findradius(w_org, rad + MAX_DAMAGEEXTRARADIUS); self; self = self.chain) { + // attached ents suck + if(self.tag_entity) + continue; + + vector nearest = NearestPointOnBox(self, w_org); if(rad) { - thisdmg = vlen(self.origin - w_org) / rad; + thisdmg = ((vlen (nearest - w_org) - bound(MIN_DAMAGEEXTRARADIUS, self.damageextraradius, MAX_DAMAGEEXTRARADIUS)) / rad); if(thisdmg >= 1) continue; + if(thisdmg < 0) + thisdmg = 0; if(dmg) { thisdmg = dmg + (edge - dmg) * thisdmg; @@ -52,6 +171,9 @@ void Ent_DamageInfo(float isNew) } else { + if(vlen(nearest - w_org) > bound(MIN_DAMAGEEXTRARADIUS, self.damageextraradius, MAX_DAMAGEEXTRARADIUS)) + continue; + thisdmg = dmg; thisforce = forcemul * force; } @@ -68,145 +190,149 @@ void Ent_DamageInfo(float isNew) if(self.event_damage) self.event_damage(thisdmg, w_deathtype, w_org, thisforce); + + DamageEffect(w_org, thisdmg, w_deathtype, species); + + if(self.isplayermodel) + hitplayer = TRUE; // this impact damaged a player } - DamageEffect(dmg, w_deathtype, species, self.entnum - 1); self = oldself; if(DEATH_ISVEHICLE(w_deathtype)) { - traceline(w_org - normalize(force) * 16, w_org + normalize(force) * 16, MOVE_NOMONSTERS, world); - if(trace_plane_normal != '0 0 0') - w_backoff = trace_plane_normal; - else - w_backoff = -1 * normalize(w_org - (w_org + normalize(force) * 16)); - - setorigin(self, w_org + w_backoff * 2); // for sound() calls - - switch(w_deathtype) - { - case DEATH_VHCRUSH: - break; - - case DEATH_SBMINIGUN: - string _snd; - _snd = strcat("weapons/ric", ftos(1 + rint(random() * 2)), ".waw"); - sound(self, CH_SHOTS, _snd, VOL_BASE, ATTN_NORM); - pointparticles(particleeffectnum("spiderbot_minigun_impact"), self.origin, w_backoff * 1000, 1); - break; - case DEATH_SBROCKET: - sound(self, CH_SHOTS, "weapons/rocket_impact.wav", VOL_BASE, ATTN_NORM); - pointparticles(particleeffectnum("spiderbot_rocket_explode"), self.origin, w_backoff * 1000, 1); - break; - case DEATH_SBBLOWUP: - sound(self, CH_SHOTS, "weapons/rocket_impact.wav", VOL_BASE, ATTN_MIN); - pointparticles(particleeffectnum("explosion_big"), self.origin, w_backoff * 1000, 1); - break; - - case DEATH_WAKIGUN: - sound(self, CH_SHOTS, "weapons/laserimpact.wav", VOL_BASE, ATTN_NORM); - pointparticles(particleeffectnum("wakizashi_gun_impact"), self.origin, w_backoff * 1000, 1); - break; - case DEATH_WAKIROCKET: - sound(self, CH_SHOTS, "weapons/rocket_impact.wav", VOL_BASE, ATTN_NORM); - pointparticles(particleeffectnum("wakizashi_rocket_explode"), self.origin, w_backoff * 1000, 1); - break; - case DEATH_WAKIBLOWUP: - sound(self, CH_SHOTS, "weapons/rocket_impact.wav", VOL_BASE, ATTN_MIN); - pointparticles(particleeffectnum("explosion_big"), self.origin, w_backoff * 1000, 1); - break; - - case DEATH_RAPTOR_CANNON: - sound(self, CH_SHOTS, "weapons/laserimpact.wav", VOL_BASE, ATTN_NORM); - pointparticles(particleeffectnum("raptor_cannon_impact"), self.origin, w_backoff * 1000, 1); - break; - case DEATH_RAPTOR_BOMB_SPLIT: - float i; - vector ang, vel; - for(i = 1; i < 4; ++i) - { - vel = normalize(w_org - (w_org + normalize(force) * 16)) + randomvec() * 128; - ang = vectoangles(vel); - RaptorCBShellfragToss(w_org, vel, ang + '0 0 1' * (120 * i)); - } - - - sound(self, CH_SHOTS, "weapons/rocket_impact.wav", VOL_BASE, ATTN_NORM); - pointparticles(particleeffectnum("raptor_bomb_spread"), self.origin, w_backoff * 1000, 1); - break; - case DEATH_RAPTOR_BOMB: - sound(self, CH_SHOTS, "weapons/rocket_impact.wav", VOL_BASE, ATTN_NORM); - pointparticles(particleeffectnum("raptor_bomb_impact"), self.origin, w_backoff * 1000, 1); - break; - case DEATH_RAPTOR_DEATH: - sound(self, CH_SHOTS, "weapons/laserimpact.wav", VOL_BASE, ATTN_MIN); - pointparticles(particleeffectnum("explosion_big"), self.origin, w_backoff * 1000, 1); - break; - } + traceline(w_org - normalize(force) * 16, w_org + normalize(force) * 16, MOVE_NOMONSTERS, world); + if(trace_plane_normal != '0 0 0') + w_backoff = trace_plane_normal; + else + w_backoff = -1 * normalize(w_org - (w_org + normalize(force) * 16)); + + setorigin(self, w_org + w_backoff * 2); // for sound() calls + + switch(w_deathtype) + { + case DEATH_VHCRUSH: + break; + + // spiderbot + case DEATH_SBMINIGUN: + string _snd; + _snd = strcat("weapons/ric", ftos(1 + rint(random() * 2)), ".waw"); + sound(self, CH_SHOTS, _snd, VOL_BASE, ATTN_NORM); + pointparticles(particleeffectnum("spiderbot_minigun_impact"), self.origin, w_backoff * 1000, 1); + break; + case DEATH_SBROCKET: + sound(self, CH_SHOTS, "weapons/rocket_impact.wav", VOL_BASE, ATTN_NORM); + pointparticles(particleeffectnum("spiderbot_rocket_explode"), self.origin, w_backoff * 1000, 1); + break; + case DEATH_SBBLOWUP: + sound(self, CH_SHOTS, "weapons/rocket_impact.wav", VOL_BASE, ATTN_MIN); + pointparticles(particleeffectnum("explosion_big"), self.origin, w_backoff * 1000, 1); + break; + + case DEATH_WAKIGUN: + sound(self, CH_SHOTS, "weapons/laserimpact.wav", VOL_BASE, ATTN_NORM); + pointparticles(particleeffectnum("wakizashi_gun_impact"), self.origin, w_backoff * 1000, 1); + break; + case DEATH_WAKIROCKET: + sound(self, CH_SHOTS, "weapons/rocket_impact.wav", VOL_BASE, ATTN_NORM); + pointparticles(particleeffectnum("wakizashi_rocket_explode"), self.origin, w_backoff * 1000, 1); + break; + case DEATH_WAKIBLOWUP: + sound(self, CH_SHOTS, "weapons/rocket_impact.wav", VOL_BASE, ATTN_MIN); + pointparticles(particleeffectnum("explosion_big"), self.origin, w_backoff * 1000, 1); + break; + + case DEATH_RAPTOR_CANNON: + sound(self, CH_SHOTS, "weapons/laserimpact.wav", VOL_BASE, ATTN_NORM); + pointparticles(particleeffectnum("raptor_cannon_impact"), self.origin, w_backoff * 1000, 1); + break; + case DEATH_RAPTOR_BOMB_SPLIT: + float i; + vector ang, vel; + for(i = 1; i < 4; ++i) + { + vel = normalize(w_org - (w_org + normalize(force) * 16)) + randomvec() * 128; + ang = vectoangles(vel); + RaptorCBShellfragToss(w_org, vel, ang + '0 0 1' * (120 * i)); + } + sound(self, CH_SHOTS, "weapons/rocket_impact.wav", VOL_BASE, ATTN_NORM); + pointparticles(particleeffectnum("raptor_bomb_spread"), self.origin, w_backoff * 1000, 1); + break; + case DEATH_RAPTOR_BOMB: + sound(self, CH_SHOTS, "weapons/rocket_impact.wav", VOL_BASE, ATTN_NORM); + pointparticles(particleeffectnum("raptor_bomb_impact"), self.origin, w_backoff * 1000, 1); + break; + case DEATH_RAPTOR_DEATH: + sound(self, CH_SHOTS, "weapons/laserimpact.wav", VOL_BASE, ATTN_MIN); + pointparticles(particleeffectnum("explosion_big"), self.origin, w_backoff * 1000, 1); + break; + } } if(DEATH_ISTURRET(w_deathtype)) - { - string _snd; - traceline(w_org - normalize(force) * 16, w_org + normalize(force) * 16, MOVE_NOMONSTERS, world); - if(trace_plane_normal != '0 0 0') - w_backoff = trace_plane_normal; - else - w_backoff = -1 * normalize(w_org - (w_org + normalize(force) * 16)); - - setorigin(self, w_org + w_backoff * 2); // for sound() calls - - switch(w_deathtype) - { - case DEATH_TURRET_EWHEEL: - sound(self, CH_SHOTS, "weapons/laserimpact.wav", VOL_BASE, ATTN_MIN); - pointparticles(particleeffectnum("laser_impact"), self.origin, w_backoff * 1000, 1); - break; - - case DEATH_TURRET_FLAC: - pointparticles(particleeffectnum("hagar_explode"), w_org, '0 0 0', 1); - _snd = strcat("weapons/hagexp", ftos(1 + rint(random() * 2)), ".waw"); - sound(self, CH_SHOTS, _snd, VOL_BASE, ATTN_NORM); - break; - - case DEATH_TURRET_MLRS: - case DEATH_TURRET_HK: - case DEATH_TURRET_WALKER_ROCKET: - case DEATH_TURRET_HELLION: - sound(self, CH_SHOTS, "weapons/rocket_impact.wav", VOL_BASE, ATTN_MIN); - pointparticles(particleeffectnum("rocket_explode"), self.origin, w_backoff * 1000, 1); - break; - - case DEATH_TURRET_MACHINEGUN: - case DEATH_TURRET_WALKER_GUN: - _snd = strcat("weapons/ric", ftos(1 + rint(random() * 2)), ".waw"); - sound(self, CH_SHOTS, _snd, VOL_BASE, ATTN_NORM); - pointparticles(particleeffectnum("machinegun_impact"), self.origin, w_backoff * 1000, 1); - break; - - case DEATH_TURRET_PLASMA: - sound(self, CH_SHOTS, "weapons/electro_impact.wav", VOL_BASE, ATTN_MIN); - pointparticles(particleeffectnum("electro_impact"), self.origin, w_backoff * 1000, 1); - break; - - case DEATH_TURRET_WALKER_MEELE: - sound(self, CH_SHOTS, "weapons/ric1.wav", VOL_BASE, ATTN_MIN); - pointparticles(particleeffectnum("TE_SPARK"), self.origin, w_backoff * 1000, 1); - break; - - case DEATH_TURRET_PHASER: - break; - - case DEATH_TURRET_TESLA: - te_smallflash(self.origin); - break; - - } + { + string _snd; + traceline(w_org - normalize(force) * 16, w_org + normalize(force) * 16, MOVE_NOMONSTERS, world); + if(trace_plane_normal != '0 0 0') + w_backoff = trace_plane_normal; + else + w_backoff = -1 * normalize(w_org - (w_org + normalize(force) * 16)); + + setorigin(self, w_org + w_backoff * 2); // for sound() calls + + switch(w_deathtype) + { + case DEATH_TURRET_EWHEEL: + sound(self, CH_SHOTS, "weapons/laserimpact.wav", VOL_BASE, ATTN_MIN); + pointparticles(particleeffectnum("laser_impact"), self.origin, w_backoff * 1000, 1); + break; + + case DEATH_TURRET_FLAC: + pointparticles(particleeffectnum("hagar_explode"), w_org, '0 0 0', 1); + _snd = strcat("weapons/hagexp", ftos(1 + rint(random() * 2)), ".waw"); + sound(self, CH_SHOTS, _snd, VOL_BASE, ATTN_NORM); + break; + + case DEATH_TURRET_MLRS: + case DEATH_TURRET_HK: + case DEATH_TURRET_WALKER_ROCKET: + case DEATH_TURRET_HELLION: + sound(self, CH_SHOTS, "weapons/rocket_impact.wav", VOL_BASE, ATTN_MIN); + pointparticles(particleeffectnum("rocket_explode"), self.origin, w_backoff * 1000, 1); + break; + + case DEATH_TURRET_MACHINEGUN: + case DEATH_TURRET_WALKER_GUN: + _snd = strcat("weapons/ric", ftos(1 + rint(random() * 2)), ".waw"); + sound(self, CH_SHOTS, _snd, VOL_BASE, ATTN_NORM); + pointparticles(particleeffectnum("machinegun_impact"), self.origin, w_backoff * 1000, 1); + break; + + case DEATH_TURRET_PLASMA: + sound(self, CH_SHOTS, "weapons/electro_impact.wav", VOL_BASE, ATTN_MIN); + pointparticles(particleeffectnum("electro_impact"), self.origin, w_backoff * 1000, 1); + break; + + case DEATH_TURRET_WALKER_MEELE: + sound(self, CH_SHOTS, "weapons/ric1.wav", VOL_BASE, ATTN_MIN); + pointparticles(particleeffectnum("TE_SPARK"), self.origin, w_backoff * 1000, 1); + break; + + case DEATH_TURRET_PHASER: + break; + + case DEATH_TURRET_TESLA: + te_smallflash(self.origin); + break; + + } } // TODO spawn particle effects and sounds based on w_deathtype if(!DEATH_ISSPECIAL(w_deathtype)) + if not(hitplayer && !rad) // don't show ground impacts for hitscan weapons if a player was hit { float hitwep; @@ -230,103 +356,3 @@ void DamageInfo_Precache() for(i = WEP_FIRST; i <= WEP_LAST; ++i) (get_weaponinfo(i)).weapon_func(WR_PRECACHE); } - -// damage effect - -.entity dmgent; -.float dmgpartnum, dmgtime; -.float lifetime; - -void DamageEffect_Think() -{ - self.nextthink = time; - - float foundgib; - vector org; - - if(time >= self.lifetime) - { - remove(self); - self = world; - return; - } - if(self.dmgtime > time) - return; - org = getplayerorigin(self.team); - if(org == GETPLAYERORIGIN_ERROR) - return; - - // Scan the owner of all gibs in the world. If a gib owner is the same as the player we're applying - // the effect to, it means our player is gibbed. Therefore, apply particles to the gibs instead. - entity head; - for(head = world; (head = find(head, classname, "gib")); ) - { - if(head.team == self.team) - { - if(autocvar_cl_damageeffect_gibs) - { - if(autocvar_cl_damageeffect_gibs_randomize >= random()) - pointparticles(self.dmgpartnum, head.origin, '0 0 0', 1); - self.dmgtime = time + autocvar_cl_damageeffect_gibs; - } - foundgib = TRUE; - } - } - - if(foundgib || !autocvar_cl_damageeffect_player) - return; // don't show effects on the invisible dead body if gibs exist - if(self.team == player_localentnum - 1 && !autocvar_chase_active) - return; // if we aren't in third person mode, hide own damage effect - - // Now apply the effect to actual players - pointparticles(self.dmgpartnum, org, '0 0 0', 1); - self.dmgtime = time + autocvar_cl_damageeffect_player; -} - -void DamageEffect(float dmg, float type, float specnum1, float entnumber) -{ - float specnum2, life; - string specstr, effectnum; - entity e; - - if(!autocvar_cl_damageeffect_player && !autocvar_cl_damageeffect_gibs) - return; - if(autocvar_cl_gentle || autocvar_cl_gentle_damage) - return; - - specnum2 = (specnum1 & 0x78) / 8; // blood type: using four bits (0..7, bit indexes 3,4,5) - specstr = species_prefix(specnum2); - life = bound(0, dmg * autocvar_cl_damageeffect_lifetime, autocvar_cl_damageeffect_lifetime_max); - - e = get_weaponinfo(type); - effectnum = strcat("weapondamage_", e.netname); - // If the weapon is a bullet weapon, its damage effect is blood. - // Since blood is species dependent, we make this effect per-species. - if(type == WEP_SHOTGUN || type == WEP_UZI || type == WEP_RIFLE) - if(specstr != "") - { - effectnum = strcat(effectnum, "_", specstr); - effectnum = substring(effectnum, 0, strlen(effectnum) - 1); // remove the _ symbol at the end of the species name - } - - // if the player already has a damage effect, update it instead of spawning a new one - entity head; - for(head = world; (head = find(head, classname, "damageeffect")); ) - { - if(head.team == entnumber) - { - head.dmgpartnum = particleeffectnum(effectnum); - head.lifetime += life; - return; - } - } - - entity e; - e = spawn(); - e.classname = "damageeffect"; - e.team = entnumber; - e.dmgpartnum = particleeffectnum(effectnum); - e.lifetime = time + life; - e.think = DamageEffect_Think; - e.nextthink = time; -}