if(!targ || !ent)
return FALSE; // someone doesn't exist
+ if(targ == ent)
+ return FALSE; // don't attack ourselves
+
+ traceline(ent.origin, targ.origin, MOVE_NORMAL, ent);
+
+ if(trace_ent != targ)
+ {
+ if(trace_ent != world)
+ targ = trace_ent;
+ else
+ return FALSE;
+ }
+
+ if(targ.vehicle_flags & VHF_ISVEHICLE)
+ if not((get_monsterinfo(ent.monsterid)).spawnflags & MON_FLAG_RANGED)
+ return FALSE; // melee attacks are useless against vehicles
+
if(time < game_starttime)
return FALSE; // monsters do nothing before the match has started
-
- WarpZone_TraceLine(ent.origin, targ.origin, MOVE_NORMAL, ent);
if(vlen(targ.origin - ent.origin) >= ent.target_range)
return FALSE; // enemy is too far away
- if not(targ.vehicle_flags & VHF_ISVEHICLE)
- if(trace_ent != targ)
- return FALSE; // we can't see the enemy
-
if(targ.takedamage == DAMAGE_NO)
return FALSE; // enemy can't be damaged
if(IS_SPEC(targ) || IS_OBSERVER(targ))
return FALSE; // enemy is a spectator
- if not(targ.vehicle_flags & VHF_ISVEHICLE) // vehicles dont count as alive?
+ if not(targ.vehicle_flags & VHF_ISVEHICLE)
if(targ.deadflag != DEAD_NO || ent.deadflag != DEAD_NO || targ.health <= 0 || ent.health <= 0)
return FALSE; // enemy/self is dead
if not(IsDifferentTeam(targ, ent))
return FALSE; // enemy is on our team
- if(autocvar_g_monsters_target_infront)
+ if(autocvar_g_monsters_target_infront || ent.spawnflags & MONSTERFLAG_INFRONT)
if(ent.enemy != targ)
{
float dot;
entity FindTarget (entity ent)
{
if(MUTATOR_CALLHOOK(MonsterFindTarget)) { return ent.enemy; } // Handled by a mutator
- entity e;
- for(e = world; (e = findflags(e, monster_attack, TRUE)); )
- if(monster_isvalidtarget(e, ent))
- return e;
-
- return world;
+ entity head, closest_target = world;
+ head = findradius(ent.origin, ent.target_range);
+
+ while(head) // find the closest acceptable target to pass to
+ {
+ if(monster_isvalidtarget(head, ent))
+ {
+ // if it's a player, use the view origin as reference (stolen from RadiusDamage functions in g_damage.qc)
+ vector head_center = CENTER_OR_VIEWOFS(head);
+ vector ent_center = CENTER_OR_VIEWOFS(ent);
+
+ //if(ctf_CheckPassDirection(head_center, ent_center, ent.v_angle, head.WarpZone_findradius_nearest))
+ if(closest_target)
+ {
+ vector closest_target_center = CENTER_OR_VIEWOFS(closest_target);
+ if(vlen(ent_center - head_center) < vlen(ent_center - closest_target_center))
+ { closest_target = head; }
+ }
+ else { closest_target = head; }
+ }
+
+ head = head.chain;
+ }
+
+ return closest_target;
}
void MonsterTouch ()
if(msound == "")
return; // sound doesn't exist
- sound(self, CHAN_AUTO, msound, VOL_BASE, ATTN_NORM);
+ sound(self, CHAN_AUTO, msound, VOL_BASE, ATTEN_NORM);
self.msound_delay = time + sound_delay;
}
if(self.msound_sight == "") self.msound_sight = strzone(strcat("monsters/", mon, "_sight.wav"));
}
-float monster_melee (entity targ, float damg, float er, float deathtype, float dostop)
+void monster_makevectors(entity e)
+{
+ vector v;
+
+ v = CENTER_OR_VIEWOFS(e);
+ self.v_angle = vectoangles(v - (self.origin + self.view_ofs));
+ self.v_angle_x = -self.v_angle_x;
+
+ makevectors(self.v_angle);
+}
+
+float monster_melee(entity targ, float damg, float anim, float er, float anim_finished, float deathtype, float dostop)
{
- float dot, rdmg = damg * random();
+ float rdmg = damg * random();
if (self.health <= 0)
- return FALSE;
- if (targ == world)
- return FALSE;
+ return FALSE; // attacking while dead?!
if(dostop)
{
self.state = MONSTER_STATE_ATTACK_MELEE;
self.SendFlags |= MSF_MOVE;
}
+
+ monsters_setframe(anim);
+
+ if(anim_finished != 0)
+ self.attack_finished_single = time + anim_finished;
- makevectors (self.angles);
- dot = normalize (targ.origin - self.origin) * v_forward;
+ monster_makevectors(targ);
+
+ traceline(self.origin + self.view_ofs, self.origin + v_forward * er, 0, self);
- if(dot > er)
- Damage(targ, self, self, rdmg * monster_skill, deathtype, targ.origin, normalize(targ.origin - self.origin));
+ if(trace_ent.takedamage)
+ Damage(trace_ent, self, self, rdmg * monster_skill, deathtype, trace_ent.origin, normalize(trace_ent.origin - self.origin));
return TRUE;
}
self.takedamage = DAMAGE_NO;
setorigin(self, self.pos1);
self.angles = self.pos2;
- self.health = self.max_health; // TODO: check if resetting to max_health is wise here
+ self.health = self.max_health;
self.SendFlags |= MSF_MOVE;
self.SendFlags |= MSF_STATUS;
-
- return;
}
- SUB_SetFade(self, time + 3, 1);
+ else
+ SUB_SetFade(self, time + 3, 1);
}
float Monster_CanJump (vector vel)
self.touch = touchfunc;
self.origin_z += 1;
self.velocity = vel;
- self.flags &~= FL_ONGROUND;
+ self.flags &= ~FL_ONGROUND;
self.attack_finished_single = time + anim_finished;
return;
}
+ if(vlen(targ.origin - e.origin) > e.attack_range)
if(e.monster_attackfunc(MONSTER_ATTACK_RANGED))
{
monster_sound(e.msound_attack_ranged, 0, FALSE);
}
}
-void monster_makevectors(entity e)
-{
- vector v;
-
- v = CENTER_OR_VIEWOFS(e);
- self.v_angle = vectoangles(v - (self.origin + self.view_ofs));
- self.v_angle_x = -self.v_angle_x;
-
- makevectors(self.v_angle);
-}
-
void monster_use ()
{
- if (self.enemy)
- return;
- if (self.health <= 0)
- return;
-
- if(!monster_isvalidtarget(activator, self))
- return;
-
- self.enemy = activator;
-}
-
-float trace_path(vector from, vector to)
-{
- vector dir = normalize(to - from) * 15, offset = '0 0 0';
- float trace1 = trace_fraction;
-
- offset_x = dir_y;
- offset_y = -dir_x;
- traceline (from+offset, to+offset, TRUE, self);
-
- traceline(from-offset, to-offset, TRUE, self);
-
- return ((trace1 < trace_fraction) ? trace1 : trace_fraction);
+ if not(self.enemy)
+ if(self.health > 0)
+ if(monster_isvalidtarget(activator, self))
+ self.enemy = activator;
}
.float last_trace;
// enemy is always preferred target
if(self.enemy)
{
+ makevectors(self.angles);
self.monster_movestate = MONSTER_MOVE_ENEMY;
self.last_trace = time + 1.2;
return self.enemy.origin;
{
self.monster_movestate = MONSTER_MOVE_OWNER;
self.last_trace = time + 0.3;
- if(self.monster_owner && self.monster_owner.classname != "td_spawnpoint")
+ if(self.monster_owner)
return self.monster_owner.origin;
}
case MONSTER_MOVE_SPAWNLOC:
self.monster_movestate = MONSTER_MOVE_WANDER;
self.last_trace = time + 2;
- self.angles_y = random() * 500;
+ self.angles_y = rint(random() * 500);
makevectors(self.angles);
pos = self.origin + v_forward * 600;
if(self.flags & FL_FLY || self.flags & FL_SWIM)
+ if(self.spawnflags & MONSTERFLAG_FLY_VERTICAL)
{
pos_z = random() * 200;
if(random() >= 0.5)
self.revive_progress = bound(0, self.revive_progress + frametime * self.revive_speed, 1);
self.health = max(1, self.max_health * self.revive_progress);
- if(self.sprite) WaypointSprite_UpdateHealth(self.sprite, self.health);
+ self.SendFlags |= MSF_STATUS;
movelib_beak_simple(stopspeed);
if(time >= self.spawn_time)
monsters_setframe(manim_idle);
movelib_beak_simple(stopspeed);
- self.SendFlags |= MSF_MOVE;
+ if(self.oldorigin != self.origin)
+ {
+ self.oldorigin = self.origin;
+ self.SendFlags |= MSF_MOVE;
+ }
return;
}
if(autocvar_g_monsters_teams)
if(IsDifferentTeam(self.monster_owner, self))
self.monster_owner = world;
+
+ if(self.enemy && self.enemy.health < 1)
+ self.enemy = world; // enough!
if(time >= self.last_enemycheck)
{
if not(monster_isvalidtarget(self.enemy, self))
self.enemy = world;
+
+ if not(self.enemy)
+ {
+ self.enemy = FindTarget(self);
+ if(self.enemy)
+ monster_sound(self.msound_sight, 0, FALSE);
+ }
+
self.last_enemycheck = time + 2;
}
-
- if(self.enemy && self.enemy.health < 1)
- self.enemy = world; // enough!
-
- if not(self.enemy)
- {
- self.enemy = FindTarget(self);
- if(self.enemy)
- monster_sound(self.msound_sight, 0, FALSE);
- }
if(self.state == MONSTER_STATE_ATTACK_MELEE && time >= self.attack_finished_single)
self.state = 0;
if(self.state == MONSTER_STATE_ATTACK_MELEE)
self.moveto = self.origin;
- else if(self.enemy)
- self.moveto = self.moveto * 0.9 + ((self.origin + v_forward * 500) + randomvec() * 400) * 0.1;
-
- if not(self.flags & FL_FLY || self.flags & FL_SWIM)
- self.moveto_z = self.origin_z;
-
- float l = vlen(self.moveto - self.origin);
- float t1 = trace_path(self.origin+'0 0 10', self.moveto+'0 0 10');
- float t2 = trace_path(self.origin-'0 0 15', self.moveto-'0 0 15');
+
+ if(self.enemy && self.enemy.vehicle)
+ runspeed = 0;
- if(self.flags & FL_FLY || self.flags & FL_SWIM)
+ if(((self.flags & FL_FLY) || (self.flags & FL_SWIM)) && self.spawnflags & MONSTERFLAG_FLY_VERTICAL)
v_forward = normalize(self.moveto - self.origin);
-
- if(t1*l-t2*l>50 && (t1*l > 100 || t1 > 0.8))
- if(self.flags & FL_ONGROUND)
- movelib_jump_simple(100);
+ else
+ self.moveto_z = self.origin_z;
if(vlen(self.origin - self.moveto) > 64)
{
movelib_move_simple(v_forward, ((self.enemy) ? runspeed : walkspeed), 0.6);
else
movelib_move_simple_gravity(v_forward, ((self.enemy) ? runspeed : walkspeed), 0.6);
+
if(time > self.pain_finished)
if(time > self.attack_finished_single)
- if(vlen(self.velocity) > 0)
+ if(vlen(self.velocity) > 10)
monsters_setframe((self.enemy) ? manim_run : manim_walk);
else
monsters_setframe(manim_idle);
self.nextthink = time + 0.3; // don't need to update so often now
self.deadflag = DEAD_DEAD;
-
+
+ if(self.ltime != 0)
if(time >= self.ltime)
{
Monster_Fade();
return;
}
- self.SendFlags |= MSF_MOVE; // keep up to date on the monster's location
+ if(self.oldorigin != self.origin)
+ {
+ self.oldorigin = self.origin;
+ self.SendFlags |= MSF_MOVE;
+ }
}
void monsters_setstatus()
void Monster_Appear()
{
self.enemy = activator;
- self.spawnflags &~= MONSTERFLAG_APPEAR;
+ self.spawnflags &= ~MONSTERFLAG_APPEAR;
self.monster_spawnfunc();
}
self.attack_finished_single = 0;
self.moveto = self.origin;
- WaypointSprite_UpdateHealth(self.sprite, self.health);
+ self.SendFlags |= MSF_STATUS;
}
float monster_send(entity to, float sf)
self.ltime = time + 5;
monster_dropitem();
-
- WaypointSprite_Kill(self.sprite);
-
- if(self.weaponentity)
- {
- remove(self.weaponentity);
- self.weaponentity = world;
- }
monster_sound(self.msound_death, 0, FALSE);
if(time < self.spawnshieldtime)
return;
- if(deathtype != DEATH_KILL)
- damage *= self.armorvalue;
+ vector v;
+ float take, save;
- if(self.weaponentity && self.weaponentity.classname == "shield")
- self.weaponentity.health -= damage;
+ v = healtharmor_applydamage(self.armorvalue, self.m_armor_blockpercent, damage);
+ take = v_x;
+ save = v_y;
- self.health -= damage;
-
- if(self.sprite)
- WaypointSprite_UpdateHealth(self.sprite, self.health);
+ self.health -= take;
self.dmg_time = time;
if(sound_allowed(MSG_BROADCAST, attacker) && deathtype != DEATH_DROWN)
- spamsound (self, CH_PAIN, "misc/bodyimpact1.wav", VOL_BASE, ATTN_NORM); // FIXME: PLACEHOLDER
+ spamsound (self, CH_PAIN, "misc/bodyimpact1.wav", VOL_BASE, ATTEN_NORM); // FIXME: PLACEHOLDER
self.velocity += force * self.damageforcescale;
if(deathtype != DEATH_DROWN)
{
- Violence_GibSplash_At(hitloc, force, 2, bound(0, damage, 200) / 16, self, attacker);
- if (damage > 50)
+ Violence_GibSplash_At(hitloc, force, 2, bound(0, take, 200) / 16, self, attacker);
+ if (take > 50)
Violence_GibSplash_At(hitloc, force * -0.1, 3, 1, self, attacker);
- if (damage > 100)
+ if (take > 100)
Violence_GibSplash_At(hitloc, force * -0.2, 3, 1, self, attacker);
}
if(self.health <= 0)
- {
- if(self.sprite)
- {
- // Update one more time to avoid waypoint fading without emptying healthbar
- WaypointSprite_UpdateHealth(self.sprite, 0);
- }
-
+ {
if(deathtype == DEATH_KILL)
self.candrop = FALSE; // killed by mobkill command
if not(self.monster_respawned)
if not(self.skin)
self.skin = rint(random() * 4);
+
+ if not(self.attack_range)
+ self.attack_range = autocvar_g_monsters_attack_range;
self.pos1 = self.origin;
if(teamplay)
self.monster_attack = TRUE; // we can have monster enemies in team games
-
- if(autocvar_g_monsters_healthbars)
- {
- WaypointSprite_Spawn(strzone(strdecolorize(self.monster_name)), 0, 600, self, '0 0 1' * (self.maxs_z + 15), world, 0, self, sprite, FALSE, RADARICON_DANGER, ((self.team) ? Team_ColorRGB(self.team) : '1 0 0'));
- WaypointSprite_UpdateMaxHealth(self.sprite, self.max_health);
- WaypointSprite_UpdateHealth(self.sprite, self.health);
- }
monster_sound(self.msound_spawn, 0, FALSE);
-
- MUTATOR_CALLHOOK(MonsterSpawn);
self.think = monster_think;
self.nextthink = time + self.ticrate;
- self.SendFlags = MSF_SETUP;
+ self.SendFlags |= MSF_SETUP;
+
+ MUTATOR_CALLHOOK(MonsterSpawn);
}
float monster_initialize(float mon_id, float nodrop)
{
if not(autocvar_g_monsters)
return FALSE;
-
- vector min_s, max_s;
+
entity mon = get_monsterinfo(mon_id);
// support for quake style removing monsters based on skill
- if(monster_skill <= autocvar_g_monsters_skill_easy && (self.spawnflags & MONSTERSKILL_NOTEASY)) { return FALSE; }
- if(monster_skill == autocvar_g_monsters_skill_normal && (self.spawnflags & MONSTERSKILL_NOTMEDIUM)) { return FALSE; }
- if(monster_skill == autocvar_g_monsters_skill_hard && (self.spawnflags & MONSTERSKILL_NOTHARD)) { return FALSE; }
- if(monster_skill == autocvar_g_monsters_skill_insane && (self.spawnflags & MONSTERSKILL_NOTINSANE)) { return FALSE; }
- if(monster_skill >= autocvar_g_monsters_skill_nightmare && (self.spawnflags & MONSTERSKILL_NOTNIGHTMARE)) { return FALSE; }
-
- if(self.monster_name == "")
- self.monster_name = M_NAME(mon_id);
+ switch(monster_skill)
+ {
+ case 0:
+ case 1: if(self.spawnflags & MONSTERSKILL_NOTEASY) return FALSE; break;
+ case 2: if(self.spawnflags & MONSTERSKILL_NOTMEDIUM) return FALSE; break;
+ default:
+ case 3: if(self.spawnflags & MONSTERSKILL_NOTHARD) return FALSE; break;
+ }
if(self.team && !teamplay)
self.team = 0;
-
- self.flags = FL_MONSTER;
-
+
if not(self.spawnflags & MONSTERFLAG_SPAWNED) // naturally spawned monster
if not(self.monster_respawned)
monsters_total += 1;
-
- min_s = mon.mins;
- max_s = mon.maxs;
- self.netname = mon.netname;
-
- setsize(self, min_s, max_s);
+ setsize(self, mon.mins, mon.maxs);
+ self.flags = FL_MONSTER;
self.takedamage = DAMAGE_AIM;
self.bot_attack = TRUE;
self.iscreature = TRUE;
self.solid = SOLID_BBOX;
self.movetype = MOVETYPE_WALK;
self.spawnshieldtime = time + autocvar_g_monsters_spawnshieldtime;
- monsters_spawned += 1;
self.enemy = world;
self.velocity = '0 0 0';
self.moveto = self.origin;
self.pos2 = self.angles;
self.reset = monsters_reset;
+ self.netname = mon.netname;
+ self.monster_name = M_NAME(mon_id);
self.candrop = TRUE;
self.view_ofs = '0 0 1' * (self.maxs_z * 0.5);
self.oldtarget2 = self.target2;
self.deadflag = DEAD_NO;
+ self.scale = 1;
self.noalign = nodrop;
self.spawn_time = time;
self.gravity = 1;
self.flags |= FL_FLY;
self.movetype = MOVETYPE_FLY;
}
-
- if not(self.scale)
- self.scale = 1;
if(mon.spawnflags & MONSTER_SIZE_BROKEN)
self.scale = 1.3;
- if not(self.attack_range)
- self.attack_range = 120;
-
if not(self.ticrate)
self.ticrate = autocvar_g_monsters_think_delay;
self.ticrate = bound(sys_frametime, self.ticrate, 60);
- if not(self.armorvalue)
- self.armorvalue = 1; // multiplier
+ if not(self.m_armor_blockpercent)
+ self.m_armor_blockpercent = 0.5;
if not(self.target_range)
self.target_range = autocvar_g_monsters_target_range;