+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;
+}
+