// c_combat.qc: entity-entity damage functions float(entity targ, entity attacker) is_same_team = { return targ != attacker && targ.team > 0 && targ.team == attacker.team; }; float(entity targ, entity attacker) are_both_players = { return targ != attacker && targ.classname == "player" && attacker.classname == "player"; }; /* ============ ent_can_damage Returns true if the inflictor can directly damage the target. Used for explosions and melee attacks. ============ */ float(entity targ, entity inflictor) ent_can_damage = { // bmodels need special checking because their origin is 0,0,0 if(targ.movetype == MOVETYPE_PUSH) { trace_line(inflictor.origin, 0.5 * (targ.absmin + targ.absmax), TRUE, self); if(trace_fraction == 1) { return TRUE; } if(trace_ent == targ) { return TRUE; } return FALSE; } trace_line(inflictor.origin, targ.origin, TRUE, self); if(trace_fraction == 1) { return TRUE; } trace_line(inflictor.origin, targ.origin + '15 15 0', TRUE, self); if(trace_fraction == 1) { return TRUE; } trace_line(inflictor.origin, targ.origin + '-15 -15 0', TRUE, self); if(trace_fraction == 1) { return TRUE; } trace_line(inflictor.origin, targ.origin + '-15 15 0', TRUE, self); if(trace_fraction == 1) { return TRUE; } trace_line(inflictor.origin, targ.origin + '15 -15 0', TRUE, self); if(trace_fraction == 1) { return TRUE; } return FALSE; }; /* ============ killed ============ */ void(entity targ, entity attacker) killed = { entity oself; oself = self; self = targ; if(self.health < -99) { self.health = -99; // don't let sbar look bad if a player } if(self.movetype == MOVETYPE_PUSH || self.movetype == MOVETYPE_NONE) { // doors, triggers, etc self.th_die(); self = oself; return; } self.enemy = attacker; // bump the monster counter if(self.flags & FL_MONSTER) { killed_monsters = killed_monsters + 1; write_byte(MSG_ALL, SVC_KILLEDMONSTER); } client_obituary(self, attacker); self.takedamage = DAMAGE_NO; self.touch = sub_null; monster_death_use(); self.th_die(); self = oself; }; /* ============ ent_damage The damage is coming from inflictor, but get mad at attacker This should be the only function that ever reduces health. ============ */ void(entity targ, entity inflictor, entity attacker, float damage) ent_damage = { vector dir; entity oldself; float save; float take; // team play damage avoidance if((teamplay == 1 && is_same_team(targ, attacker)) || (teamplay == 3 && are_both_players(targ, attacker)) || !targ.takedamage) { return; } // used by buttons and triggers to set activator for target firing damage_attacker = attacker; // check for quad damage powerup on the attacker if(attacker.super_damage_finished > time) { damage = damage * 4; } // save damage based on the target's armor level save = ceil(targ.armortype * damage); if(save >= targ.armorvalue) { save = targ.armorvalue; targ.armortype = 0; // lost all armor targ.items = targ.items - (targ.items & (IT_ARMOR1 | IT_ARMOR2 | IT_ARMOR3)); } targ.armorvalue = targ.armorvalue - save; take = ceil(damage - save); // add to the damage total for clients, which will be sent as a single // message at the end of the frame if(targ.flags & FL_CLIENT) { targ.dmg_take += take; targ.dmg_save += save; targ.dmg_inflictor = inflictor; } // figure momentum add if((inflictor != world) && (targ.movetype == MOVETYPE_WALK)) { dir = targ.origin - (inflictor.absmin + inflictor.absmax) * 0.5; dir = normalize(dir); targ.velocity = targ.velocity + dir * damage * 8; } // check for godmode or invincibility if(targ.flags & FL_GODMODE) { return; } if(targ.invincible_finished >= time) { if(self.invincible_sound < time) { sound(targ, CHAN_ITEM, "items/protect3.wav", 1, ATTN_NORM); self.invincible_sound = time + 2; } return; } // do the damage targ.health = targ.health - take; if(targ.health <= 0) { killed(targ, attacker); return; } // react to the damage oldself = self; self = targ; if(self.flags & FL_MONSTER && attacker != world) { // get mad unless of the same class (except for soldiers) if(self != attacker && attacker != self.enemy) { if((self.classname != attacker.classname) || (self.classname == "monster_army")) { if(self.enemy.classname == "player") { self.oldenemy = self.enemy; } self.enemy = attacker; found_target(); } } } if(self.th_pain) { self.th_pain(attacker, take); // nightmare mode monsters don't go into pain frames often if(skill == SK_NIGHTMARE) { self.pain_finished = time + 5; } } self = oldself; }; /* ============ ent_radius_damage ============ */ void(entity inflictor, entity attacker, float damage, entity ignore) ent_radius_damage = { float points; entity head; vector org; float attacker_damaged; float attacker_points; head = find_radius(inflictor.origin, damage + 40); while(head) { if(head != ignore && head.takedamage) { org = head.origin + (head.mins + head.maxs) * 0.5; points = 0.5 * vec_len(inflictor.origin - org); if(points < 0) { points = 0; } points = damage - points; if(points > 0 && ent_can_damage(head, inflictor)) { if(head != attacker) { // shambler takes half damage from all explosions if(head.classname == "monster_shambler") { ent_damage(head, inflictor, attacker, points * 0.5); } else { ent_damage(head, inflictor, attacker, points); } } else { attacker_damaged = TRUE; attacker_points = points * 0.5; } } } head = head.chain; } if(attacker_damaged) { ent_damage(attacker, inflictor, attacker, attacker_points); } }; /* ============ beam_damage ============ */ void(entity attacker, float damage) beam_damage = { float points; entity head; head = find_radius(attacker.origin, damage + 40); while(head) { if(head.takedamage) { points = 0.5 * vec_len(attacker.origin - head.origin); if(points < 0) { points = 0; } points = damage - points; if(head == attacker) { points = points * 0.5; } if(points > 0) { if(ent_can_damage(head, attacker)) { if(head.classname == "monster_shambler") { ent_damage(head, attacker, attacker, points * 0.5); } else { ent_damage(head, attacker, attacker, points); } } } } head = head.chain; } };