#include "../../server/defs.qh"
#include "../deathtypes.qh"
#include "../../server/mutators/mutators_include.qh"
- #include "../../server/tturrets/include/turrets_early.qh"
- #include "../../server/steerlib.qh"
- #include "../../server/vehicles/vehicle.qh"
+ #include "../../server/steerlib.qh"
+ #include "../turrets/sv_turrets.qh"
+ #include "../turrets/util.qh"
+ #include "../vehicles/all.qh"
#include "../../server/campaign.qh"
#include "../../server/command/common.qh"
#include "../../server/command/cmd.qh"
#include "../triggers/triggers.qh"
#include "../../csqcmodellib/sv_model.qh"
#include "../../server/round_handler.qh"
- #include "../../server/tturrets/include/turrets.qh"
#endif
void monsters_setstatus()
-{
+{SELFPARAM();
self.stat_monsters_total = monsters_total;
self.stat_monsters_killed = monsters_killed;
}
void monster_dropitem()
-{
+{SELFPARAM();
if(!self.candrop || !self.monster_loot)
return;
vector org = self.origin + ((self.mins + self.maxs) * 0.5);
- entity e = spawn(), oldself = self;
+ entity e = spawn();
e.monster_loot = self.monster_loot;
- other = e;
- MUTATOR_CALLHOOK(MonsterDropItem);
+ MUTATOR_CALLHOOK(MonsterDropItem, e);
e = other;
if(e && e.monster_loot)
{
- self = e;
+ setself(e);
e.noalign = true;
- e.monster_loot();
+ e.monster_loot(e);
e.gravity = 1;
e.movetype = MOVETYPE_TOSS;
e.reset = SUB_Remove;
e.item_spawnshieldtime = time + 0.7;
e.classname = "droppedweapon"; // use weapon handling to remove it on touch
SUB_SetFade(e, time + autocvar_g_monsters_drop_time, 1);
- self = oldself;
+ setself(this);
}
}
void monster_makevectors(entity e)
-{
- if(self.flags & FL_MONSTER)
+{SELFPARAM();
+ if(IS_MONSTER(self))
{
vector v;
// ===============
bool Monster_ValidTarget(entity mon, entity player)
-{
+{SELFPARAM();
// ensure we're not checking nonexistent monster/target
if(!mon || !player) { return false; }
if((player == mon)
|| (autocvar_g_monsters_lineofsight && !checkpvs(mon.origin + mon.view_ofs, player)) // enemy cannot be seen
- || ((player.vehicle_flags & VHF_ISVEHICLE) && !((get_monsterinfo(mon.monsterid)).spawnflags & MON_FLAG_RANGED)) // melee vs vehicle is useless
+ || (IS_VEHICLE(player) && !((get_monsterinfo(mon.monsterid)).spawnflags & MON_FLAG_RANGED)) // melee vs vehicle is useless
|| (time < game_starttime) // monsters do nothing before match has started
|| (player.takedamage == DAMAGE_NO)
|| (player.items & IT_INVISIBILITY)
|| (IS_SPEC(player) || IS_OBSERVER(player)) // don't attack spectators
- || (!(player.vehicle_flags & VHF_ISVEHICLE) && (player.deadflag != DEAD_NO || mon.deadflag != DEAD_NO || player.health <= 0 || mon.health <= 0))
+ || (!IS_VEHICLE(player) && (player.deadflag != DEAD_NO || mon.deadflag != DEAD_NO || player.health <= 0 || mon.health <= 0))
|| (mon.monster_follow == player || player.monster_follow == mon)
- || (!(player.vehicle_flags & VHF_ISVEHICLE) && (player.flags & FL_NOTARGET))
+ || (!IS_VEHICLE(player) && (player.flags & FL_NOTARGET))
|| (!autocvar_g_monsters_typefrag && player.BUTTON_CHAT)
|| (SAME_TEAM(player, mon))
|| (player.frozen)
makevectors (mon.angles);
dot = normalize (player.origin - mon.origin) * v_forward;
- if(dot <= 0.3) { return false; }
+ if(dot <= autocvar_g_monsters_target_infront_range) { return false; }
}
return true; // this target is valid!
void monster_changeteam(entity ent, float newteam)
{
if(!teamplay) { return; }
-
+
ent.team = newteam;
ent.monster_attack = true; // new team, activate attacking
monster_setupcolors(ent);
-
+
if(ent.sprite)
{
WaypointSprite_UpdateTeamRadar(ent.sprite, RADARICON_DANGER, ((newteam) ? Team_ColorRGB(newteam) : '1 0 0'));
}
void Monster_Delay_Action()
-{
+{SELFPARAM();
entity oldself = self;
- self = self.owner;
+ setself(self.owner);
if(Monster_ValidTarget(self, self.enemy)) { oldself.use(); }
if(oldself.cnt > 0)
}
void Monster_Delay(float repeat_count, float repeat_defer, float defer_amnt, void() func)
-{
+{SELFPARAM();
// deferred attacking, checks if monster is still alive and target is still valid before attacking
entity e = spawn();
{
if(tokenize_console(s) != 3)
{
- dprint("Invalid sound info line: ", s, "\n");
+ LOG_TRACE("Invalid sound info line: ", s, "\n");
continue;
}
PrecacheGlobalSound(strcat(argv(1), " ", argv(2)));
}
void Monster_Sounds_Precache()
-{
+{SELFPARAM();
string m = (get_monsterinfo(self.monsterid)).model;
float globhandle, n, i;
string f;
}
void Monster_Sounds_Clear()
-{
+{SELFPARAM();
#define _MSOUND(m) if(self.monstersound_##m) { strunzone(self.monstersound_##m); self.monstersound_##m = string_null; }
ALLMONSTERSOUNDS
#undef _MSOUND
return string_null;
}
-float Monster_Sounds_Load(string f, float first)
-{
+bool Monster_Sounds_Load(string f, int first)
+{SELFPARAM();
float fh;
string s;
var .string field;
fh = fopen(f, FILE_READ);
if(fh < 0)
{
- dprint("Monster sound file not found: ", f, "\n");
+ LOG_TRACE("Monster sound file not found: ", f, "\n");
return false;
}
while((s = fgets(fh)))
.int skin_for_monstersound;
void Monster_Sounds_Update()
-{
+{SELFPARAM();
if(self.skin == self.skin_for_monstersound) { return; }
self.skin_for_monstersound = self.skin;
}
void Monster_Sound(.string samplefield, float sound_delay, float delaytoo, float chan)
-{
+{SELFPARAM();
if(!autocvar_g_monsters_sounds) { return; }
if(delaytoo)
// =======================
float Monster_Attack_Melee(entity targ, float damg, vector anim, float er, float animtime, int deathtype, float dostop)
-{
+{SELFPARAM();
if(dostop && (self.flags & FL_MONSTER)) { self.state = MONSTER_ATTACK_MELEE; }
setanim(self, anim, false, true, false);
}
float Monster_Attack_Leap_Check(vector vel)
-{
+{SELFPARAM();
if(self.state && (self.flags & FL_MONSTER))
return false; // already attacking
if(!(self.flags & FL_ONGROUND))
}
bool Monster_Attack_Leap(vector anm, void() touchfunc, vector vel, float animtime)
-{
+{SELFPARAM();
if(!Monster_Attack_Leap_Check(vel))
return false;
// Main monster functions
// ======================
+void Monster_UpdateModel()
+{SELFPARAM();
+ // assume some defaults
+ /*self.anim_idle = animfixfps(self, '0 1 0.01', '0 0 0');
+ self.anim_walk = animfixfps(self, '1 1 0.01', '0 0 0');
+ self.anim_run = animfixfps(self, '2 1 0.01', '0 0 0');
+ self.anim_fire1 = animfixfps(self, '3 1 0.01', '0 0 0');
+ self.anim_fire2 = animfixfps(self, '4 1 0.01', '0 0 0');
+ self.anim_melee = animfixfps(self, '5 1 0.01', '0 0 0');
+ self.anim_pain1 = animfixfps(self, '6 1 0.01', '0 0 0');
+ self.anim_pain2 = animfixfps(self, '7 1 0.01', '0 0 0');
+ self.anim_die1 = animfixfps(self, '8 1 0.01', '0 0 0');
+ self.anim_die2 = animfixfps(self, '9 1 0.01', '0 0 0');*/
+
+ // then get the real values
+ MON_ACTION(self.monsterid, MR_ANIM);
+}
+
void Monster_Touch()
-{
+{SELFPARAM();
if(other == world) { return; }
if(other.monster_attack)
if(self.enemy != other)
- if(!(self.flags & FL_MONSTER))
+ if(!IS_MONSTER(other))
if(Monster_ValidTarget(self, other))
self.enemy = other;
}
void Monster_Miniboss_Check()
-{
+{SELFPARAM();
if(MUTATOR_CALLHOOK(MonsterCheckBossFlag))
return;
self.health += autocvar_g_monsters_miniboss_healthboost;
self.effects |= EF_RED;
if(!self.weapon)
- self.weapon = WEP_VORTEX;
+ self.weapon = WEP_VORTEX.m_id;
}
}
-float Monster_Respawn_Check()
-{
- if(self.spawnflags & MONSTERFLAG_NORESPAWN) { return false; }
- if(!autocvar_g_monsters_respawn) { return false; }
+bool Monster_Respawn_Check()
+{SELFPARAM();
+ if(self.deadflag == DEAD_DEAD) // don't call when monster isn't dead
+ if(MUTATOR_CALLHOOK(MonsterRespawn, self))
+ return true; // enabled by a mutator
+
+ if(self.spawnflags & MONSTERFLAG_NORESPAWN)
+ return false;
+
+ if(!autocvar_g_monsters_respawn)
+ return false;
return true;
}
-void Monster_Respawn() { Monster_Spawn(self.monsterid); }
+void Monster_Respawn() { SELFPARAM(); Monster_Spawn(self.monsterid); }
void Monster_Dead_Fade()
-{
+{SELFPARAM();
if(Monster_Respawn_Check())
{
self.spawnflags |= MONSTERFLAG_RESPAWNED;
setorigin(self, self.pos1);
self.angles = self.pos2;
self.health = self.max_health;
- setmodel(self, "null");
+ setmodel(self, MDL_Null);
}
else
{
}
void Monster_Use()
-{
+{SELFPARAM();
if(Monster_ValidTarget(self, activator)) { self.enemy = activator; }
}
vector Monster_Move_Target(entity targ)
-{
+{SELFPARAM();
// enemy is always preferred target
if(self.enemy)
{
if(self.enemy)
{
- /*WarpZone_TrailParticles(world, particleeffectnum("red_pass"), self.origin, targ_origin);
+ /*WarpZone_TrailParticles(world, particleeffectnum(EFFECT_RED_PASS), self.origin, targ_origin);
print("Trace origin: ", vtos(targ_origin), "\n");
print("Target origin: ", vtos(self.enemy.origin), "\n");
print("My origin: ", vtos(self.origin), "\n"); */
}
void Monster_CalculateVelocity(entity mon, vector to, vector from, float turnrate, float movespeed)
-{
+{SELFPARAM();
float current_distance = vlen((('1 0 0' * to.x) + ('0 1 0' * to.y)) - (('1 0 0' * from.x) + ('0 1 0' * from.y))); // for the sake of this check, exclude Z axis
float initial_height = 0; //min(50, (targ_distance * tanh(20)));
float current_height = (initial_height * min(1, (self.pass_distance) ? (current_distance / self.pass_distance) : current_distance));
}
void Monster_Move(float runspeed, float walkspeed, float stpspeed)
-{
+{SELFPARAM();
if(self.target2) { self.goalentity = find(world, targetname, self.target2); }
entity targ;
targ = self.goalentity;
- monster_target = targ;
- monster_speed_run = runspeed;
- monster_speed_walk = walkspeed;
-
- if((MUTATOR_CALLHOOK(MonsterMove))
- || (gameover)
- || (self.draggedby != world)
- || (round_handler_IsActive() && !round_handler_IsRoundStarted())
- || (time < game_starttime)
- || (autocvar_g_campaign && !campaign_bots_may_start)
- || (time < self.spawn_time)
- )
+ if (MUTATOR_CALLHOOK(MonsterMove, runspeed, walkspeed, targ)
+ || gameover
+ || self.draggedby != world
+ || (round_handler_IsActive() && !round_handler_IsRoundStarted())
+ || time < game_starttime
+ || (autocvar_g_campaign && !campaign_bots_may_start)
+ || time < self.spawn_time)
{
runspeed = walkspeed = 0;
if(time >= self.spawn_time)
self.moveto = WarpZone_RefSys_TransformOrigin(self.enemy, self, (0.5 * (self.enemy.absmin + self.enemy.absmax)));
self.monster_moveto = '0 0 0';
self.monster_face = '0 0 0';
-
+
self.pass_distance = vlen((('1 0 0' * self.enemy.origin_x) + ('0 1 0' * self.enemy.origin_y)) - (('1 0 0' * self.origin_x) + ('0 1 0' * self.origin_y)));
Monster_Sound(monstersound_sight, 0, false, CH_VOICE);
}
if(vlen(self.origin - self.moveto) > 100)
{
- float do_run = !!(self.enemy);
+ float do_run = (self.enemy || self.monster_moveto);
if((self.flags & FL_ONGROUND) || ((self.flags & FL_FLY) || (self.flags & FL_SWIM)))
Monster_CalculateVelocity(self, self.moveto, self.origin, true, ((do_run) ? runspeed : walkspeed));
{
if(!mon) { return; }
- if(!MUTATOR_CALLHOOK(MonsterRemove))
- pointparticles(particleeffectnum("item_pickup"), mon.origin, '0 0 0', 1);
+ if(!MUTATOR_CALLHOOK(MonsterRemove, mon))
+ Send_Effect(EFFECT_ITEM_PICKUP, mon.origin, '0 0 0', 1);
if(mon.weaponentity) { remove(mon.weaponentity); }
if(mon.iceblock) { remove(mon.iceblock); }
}
void Monster_Dead_Think()
-{
+{SELFPARAM();
self.nextthink = time + self.ticrate;
if(self.monster_lifetime != 0)
}
void Monster_Appear()
-{
+{SELFPARAM();
self.enemy = activator;
self.spawnflags &= ~MONSTERFLAG_APPEAR; // otherwise, we get an endless loop
Monster_Spawn(self.monsterid);
}
void Monster_Reset()
-{
+{SELFPARAM();
setorigin(self, self.pos1);
self.angles = self.pos2;
}
void Monster_Dead_Damage(entity inflictor, entity attacker, float damage, int deathtype, vector hitloc, vector force)
-{
+{SELFPARAM();
self.health -= damage;
Violence_GibSplash_At(hitloc, force, 2, bound(0, damage, 200) / 16, self, attacker);
}
void Monster_Dead(entity attacker, float gibbed)
-{
+{SELFPARAM();
self.think = Monster_Dead_Think;
self.nextthink = time;
self.monster_lifetime = time + 5;
}
void Monster_Damage(entity inflictor, entity attacker, float damage, int deathtype, vector hitloc, vector force)
-{
- if((self.spawnflags & MONSTERFLAG_INVINCIBLE) && deathtype != DEATH_KILL)
+{SELFPARAM();
+ if((self.spawnflags & MONSTERFLAG_INVINCIBLE) && deathtype != DEATH_KILL && !ITEM_DAMAGE_NEEDKILL(deathtype))
return;
if(self.frozen && deathtype != DEATH_KILL && deathtype != DEATH_NADE_ICE_FREEZE)
self.dmg_time = time;
if(sound_allowed(MSG_BROADCAST, attacker) && deathtype != DEATH_DROWN)
- spamsound (self, CH_PAIN, "misc/bodyimpact1.wav", VOL_BASE, ATTEN_NORM); // FIXME: PLACEHOLDER
+ spamsound (self, CH_PAIN, SND(BODYIMPACT1), VOL_BASE, ATTEN_NORM); // FIXME: PLACEHOLDER
self.velocity += force * self.damageforcescale;
WaypointSprite_Kill(self.sprite);
- frag_attacker = attacker;
frag_target = self;
- MUTATOR_CALLHOOK(MonsterDies);
+ MUTATOR_CALLHOOK(MonsterDies, attacker);
if(self.health <= -100 || deathtype == DEATH_KILL) // check if we're already gibbed
{
// don't check for enemies, just keep walking in a straight line
void Monster_Move_2D(float mspeed, float allow_jumpoff)
-{
+{SELFPARAM();
if(gameover || (round_handler_IsActive() && !round_handler_IsRoundStarted()) || self.draggedby != world || time < game_starttime || (autocvar_g_campaign && !campaign_bots_may_start) || time < self.spawn_time)
{
mspeed = 0;
float reverse = FALSE;
vector a, b;
-
+
makevectors(self.angles);
a = self.origin + '0 0 16';
b = self.origin + '0 0 16' + v_forward * 32;
-
+
traceline(a, b, MOVE_NORMAL, self);
-
+
if(trace_fraction != 1.0)
{
reverse = TRUE;
-
+
if(trace_ent)
if(IS_PLAYER(trace_ent) && !(trace_ent.items & IT_STRENGTH))
reverse = FALSE;
}
-
+
// TODO: fix this... tracing is broken if the floor is thin
/*
if(!allow_jumpoff)
if(trace_fraction == 1.0)
reverse = TRUE;
} */
-
+
if(reverse)
{
self.angles_y = anglemods(self.angles_y - 180);
makevectors(self.angles);
}
-
+
movelib_move_simple_gravity(v_forward, mspeed, 1);
-
+
if(time > self.pain_finished)
if(time > self.attack_finished_single)
if(vlen(self.velocity) > 10)
setanim(self, self.anim_idle, true, false, false);
}
+void Monster_Anim()
+{SELFPARAM();
+ int deadbits = (self.anim_state & (ANIMSTATE_DEAD1 | ANIMSTATE_DEAD2));
+ if(self.deadflag)
+ {
+ if (!deadbits)
+ {
+ // Decide on which death animation to use.
+ if(random() < 0.5)
+ deadbits = ANIMSTATE_DEAD1;
+ else
+ deadbits = ANIMSTATE_DEAD2;
+ }
+ }
+ else
+ {
+ // Clear a previous death animation.
+ deadbits = 0;
+ }
+ int animbits = deadbits;
+ if(self.frozen)
+ animbits |= ANIMSTATE_FROZEN;
+ if(self.crouch)
+ animbits |= ANIMSTATE_DUCK; // not that monsters can crouch currently...
+ animdecide_setstate(self, animbits, false);
+ animdecide_setimplicitstate(self, (self.flags & FL_ONGROUND));
+
+ /* // weapon entities for monsters?
+ if (self.weaponentity)
+ {
+ updateanim(self.weaponentity);
+ if (!self.weaponentity.animstate_override)
+ setanim(self.weaponentity, self.weaponentity.anim_idle, true, false, false);
+ }
+ */
+}
+
void Monster_Think()
-{
+{SELFPARAM();
self.think = Monster_Think;
self.nextthink = self.ticrate;
if(MON_ACTION(self.monsterid, MR_THINK))
Monster_Move(self.speed2, self.speed, self.stopspeed);
- CSQCMODEL_AUTOUPDATE();
+ Monster_Anim();
+
+ CSQCMODEL_AUTOUPDATE(self);
}
float Monster_Spawn_Setup()
-{
+{SELFPARAM();
MON_ACTION(self.monsterid, MR_SETUP);
// ensure some basic needs are met
if(autocvar_g_monsters_healthbars)
{
- WaypointSprite_Spawn(self.monster_name, 0, 1024, self, '0 0 1' * (self.maxs_z + 15), world, self.team,
- self, sprite, true, RADARICON_DANGER, ((self.team) ? Team_ColorRGB(self.team) : '1 0 0'));
-
+ entity wp = WaypointSprite_Spawn(WP_Monster, 0, 1024, self, '0 0 1' * (self.maxs.z + 15), world, self.team, self, sprite, true, RADARICON_DANGER);
+ wp.wp_extra = self.monsterid;
+ wp.colormod = ((self.team) ? Team_ColorRGB(self.team) : '1 0 0');
if(!(self.spawnflags & MONSTERFLAG_INVINCIBLE))
{
WaypointSprite_UpdateMaxHealth(self.sprite, self.max_health);
}
bool Monster_Spawn(int mon_id)
-{
+{SELFPARAM();
// setup the basic required properties for a monster
entity mon = get_monsterinfo(mon_id);
if(!mon.monsterid) { return false; } // invalid monster
if(!(self.spawnflags & MONSTERFLAG_RESPAWNED)) // don't count re-spawning monsters either
monsters_total += 1;
- setmodel(self, self.mdl);
+ _setmodel(self, self.mdl);
self.flags = FL_MONSTER;
self.classname = "monster";
self.takedamage = DAMAGE_AIM;
self.monster_moveto = '0 0 0';
self.monster_face = '0 0 0';
self.dphitcontentsmask = DPCONTENTS_SOLID | DPCONTENTS_BODY | DPCONTENTS_BOTCLIP | DPCONTENTS_MONSTERCLIP;
-
+
if(!self.scale) { self.scale = 1; }
if(autocvar_g_monsters_edit) { self.grab = 1; }
if(autocvar_g_fullbrightplayers) { self.effects |= EF_FULLBRIGHT; }
self.ticrate = bound(sys_frametime, ((!self.ticrate) ? autocvar_g_monsters_think_delay : self.ticrate), 60);
+ Monster_UpdateModel();
+
if(!Monster_Spawn_Setup())
{
Monster_Remove(self);
if(!(self.spawnflags & MONSTERFLAG_RESPAWNED))
monster_setupcolors(self);
- CSQCMODEL_AUTOINIT();
+ CSQCMODEL_AUTOINIT(self);
return true;
}