if(self.isplayermodel) // this checks if it's a player MODEL!
{
- CSQCPlayer_ModelAppearance_Apply(self.entnum == player_localnum + 1);
- CSQCPlayer_LOD_Apply();
+ if(isplayer)
+ {
+ CSQCPlayer_ModelAppearance_Apply(self.entnum == player_localnum + 1);
+ CSQCPlayer_LOD_Apply();
+ }
+
if(!isplayer)
{
skeleton_loadinfo(self);
#include "../server/defs.qh"
#endif
-// player animation data for this model
-// each vector is as follows:
-// _x = startframe
-// _y = numframes
-// _z = framerate
-.vector anim_die1; // player dies
-.vector anim_die2; // player dies differently
-.vector anim_draw; // player pulls out a weapon
-.vector anim_duckwalk; // player walking while crouching
-.vector anim_duckjump; // player jumping from a crouch
-.vector anim_duckidle; // player idling while crouching
-.vector anim_idle; // player standing
-.vector anim_jump; // player jump
-.vector anim_pain1; // player flinches from pain
-.vector anim_pain2; // player flinches from pain, differently
-.vector anim_shoot; // player shoots
-.vector anim_taunt; // player taunts others (FIXME: no code references this)
-.vector anim_run; // player running forward
-.vector anim_runbackwards; // player running backward
-.vector anim_strafeleft; // player shuffling left quickly
-.vector anim_straferight; // player shuffling right quickly
-.vector anim_forwardright; // player running forward and right
-.vector anim_forwardleft; // player running forward and left
-.vector anim_backright; // player running backward and right
-.vector anim_backleft; // player running back and left
-.vector anim_melee; // player doing the melee action
-.vector anim_duck; // player doing the melee action
-.vector anim_duckwalkbackwards;
-.vector anim_duckwalkstrafeleft;
-.vector anim_duckwalkstraferight;
-.vector anim_duckwalkforwardright;
-.vector anim_duckwalkforwardleft;
-.vector anim_duckwalkbackright;
-.vector anim_duckwalkbackleft;
-.float animdecide_modelindex;
-
void animdecide_load_if_needed(entity e)
{
if(e.modelindex == e.animdecide_modelindex)
void animdecide_setimplicitstate(entity e, float onground);
void animdecide_setframes(entity e, bool support_blending, .int fld_frame, .int fld_frame1time, .int fld_frame2, .int fld_frame2time);
+// player animation data for this model
+// each vector is as follows:
+// _x = startframe
+// _y = numframes
+// _z = framerate
+.vector anim_die1; // player dies
+.vector anim_die2; // player dies differently
+.vector anim_draw; // player pulls out a weapon
+.vector anim_duckwalk; // player walking while crouching
+.vector anim_duckjump; // player jumping from a crouch
+.vector anim_duckidle; // player idling while crouching
+.vector anim_idle; // player standing
+.vector anim_jump; // player jump
+.vector anim_pain1; // player flinches from pain
+.vector anim_pain2; // player flinches from pain, differently
+.vector anim_shoot; // player shoots
+.vector anim_taunt; // player taunts others (FIXME: no code references this)
+.vector anim_run; // player running forward
+.vector anim_runbackwards; // player running backward
+.vector anim_strafeleft; // player shuffling left quickly
+.vector anim_straferight; // player shuffling right quickly
+.vector anim_forwardright; // player running forward and right
+.vector anim_forwardleft; // player running forward and left
+.vector anim_backright; // player running backward and right
+.vector anim_backleft; // player running back and left
+.vector anim_melee; // player doing the melee action
+.vector anim_duck; // player doing the melee action
+.vector anim_duckwalkbackwards;
+.vector anim_duckwalkstrafeleft;
+.vector anim_duckwalkstraferight;
+.vector anim_duckwalkforwardright;
+.vector anim_duckwalkforwardleft;
+.vector anim_duckwalkbackright;
+.vector anim_duckwalkbackleft;
+.float animdecide_modelindex;
+
// please network this one
.int anim_state;
.float anim_time;
+#ifndef MENUQC
+#include "../animdecide.qh"
+vector animfixfps(entity e, vector a, vector b);
+#endif
+
#include "monster/zombie.qc"
#include "monster/spider.qc"
#include "monster/mage.qc"
entity monster_info[MON_MAXCOUNT];
entity dummy_monster_info;
-void register_monster(int id, float(float) func, int monsterflags, vector min_s, vector max_s, string modelname, string shortname, string mname)
+void register_monster(int id, float(float) func, int(float) attackfunc, int monsterflags, vector min_s, vector max_s, string modelname, string shortname, string mname)
{
entity e;
monster_info[id - 1] = e = spawn();
e.netname = shortname;
e.monster_name = mname;
e.monster_func = func;
+ e.monster_attackfunc = attackfunc;
e.mdl = modelname;
e.spawnflags = monsterflags;
e.mins = min_s;
dummy_monster_info.netname = "";
dummy_monster_info.monster_name = "Monster";
dummy_monster_info.monster_func = m_null;
+ dummy_monster_info.monster_attackfunc = m_null;
dummy_monster_info.mdl = "";
dummy_monster_info.mins = '-0 -0 -0';
dummy_monster_info.maxs = '0 0 0';
const int MR_THINK = 2; // (SERVER) logic to run every frame
const int MR_DEATH = 3; // (SERVER) called when monster dies
const int MR_PRECACHE = 4; // (BOTH) precaches models/sounds used by this monster
+const int MR_PAIN = 5; // (SERVER) called when monster is damaged
+const int MR_ANIM = 6; // (BOTH?) sets animations for monster
-// functions:
+// functions
entity get_monsterinfo(float id);
// special spawn flags
const int MON_FLAG_RANGED = 512; // monster shoots projectiles
const int MON_FLAG_MELEE = 1024;
-// entity properties of monsterinfo:
+// entity properties of monsterinfo
.float monsterid; // MON_...
.string netname; // short name
.string monster_name; // human readable name
-.float(float) monster_func; // m_...
+.float(float) monster_func; // M_...
+.float(float attack_type) monster_attackfunc; // attack function
.string mdl; // currently a copy of the model
.string model; // full name of model
.int spawnflags;
.vector mins, maxs; // monster hitbox size
+// animations
+.vector anim_blockend;
+.vector anim_blockstart;
+.vector anim_melee1;
+.vector anim_melee2;
+.vector anim_melee3;
+.vector anim_pain3;
+.vector anim_pain4;
+.vector anim_pain5;
+.vector anim_walk;
+.vector anim_spawn;
+
// other useful macros
#define MON_ACTION(monstertype,mrequest) (get_monsterinfo(monstertype)).monster_func(mrequest)
-#define M_NAME(monstertype) (get_monsterinfo(monstertype)).monster_name
// =====================
// Monster Registration
// =====================
float m_null(float dummy);
-void register_monster(float id, float(float) func, float monsterflags, vector min_s, vector max_s, string modelname, string shortname, string mname);
+void register_monster(float id, float(float) func, float(float) attackfunc, float monsterflags, vector min_s, vector max_s, string modelname, string shortname, string mname);
void register_monsters_done();
-const int MON_MAXCOUNT = 24;
+const int MON_MAXCOUNT = 24; // increase as necessary, limit is infinite, but keep loops small!
const int MON_FIRST = 1;
int MON_COUNT;
int MON_LAST;
-#define REGISTER_MONSTER_2(id,func,monsterflags,min_s,max_s,modelname,shortname,mname) \
+#define REGISTER_MONSTER_2(id,func,attackfunc,monsterflags,min_s,max_s,modelname,shortname,mname) \
int id; \
float func(float); \
+ float attackfunc(float); \
void RegisterMonsters_##id() \
{ \
MON_LAST = (id = MON_FIRST + MON_COUNT); \
++MON_COUNT; \
- register_monster(id,func,monsterflags,min_s,max_s,modelname,shortname,mname); \
+ register_monster(id,func,attackfunc,monsterflags,min_s,max_s,modelname,shortname,mname); \
} \
ACCUMULATE_FUNCTION(RegisterMonsters, RegisterMonsters_##id)
-#ifdef MENUQC
-#define REGISTER_MONSTER(id,func,monsterflags,min_s,max_s,modelname,shortname,mname) \
- REGISTER_MONSTER_2(MON_##id,m_null,monsterflags,min_s,max_s,modelname,shortname,mname)
+#ifdef SVQC
+#define REGISTER_MONSTER(id,func,attackfunc,monsterflags,min_s,max_s,modelname,shortname,mname) \
+ REGISTER_MONSTER_2(MON_##id,func,attackfunc,monsterflags,min_s,max_s,modelname,shortname,mname)
+#elif defined(CSQC)
+#define REGISTER_MONSTER(id,func,attackfunc,monsterflags,min_s,max_s,modelname,shortname,mname) \
+ REGISTER_MONSTER_2(MON_##id,func,m_null,monsterflags,min_s,max_s,modelname,shortname,mname)
#else
-#define REGISTER_MONSTER(id,func,monsterflags,min_s,max_s,modelname,shortname,mname) \
- REGISTER_MONSTER_2(MON_##id,func,monsterflags,min_s,max_s,modelname,shortname,mname)
+#define REGISTER_MONSTER(id,func,attackfunc,monsterflags,min_s,max_s,modelname,shortname,mname) \
+ REGISTER_MONSTER_2(MON_##id,m_null,m_null,monsterflags,min_s,max_s,modelname,shortname,mname)
#endif
#include "all.inc"
#ifdef REGISTER_MONSTER
REGISTER_MONSTER(
/* MON_##id */ MAGE,
-/* function */ m_mage,
+/* functions */ M_Mage, M_Mage_Attack,
/* spawnflags */ MON_FLAG_MELEE | MON_FLAG_RANGED,
/* mins,maxs */ '-36 -36 -24', '36 36 50',
/* model */ "mage.dpm",
#else
#ifdef SVQC
float autocvar_g_monster_mage_health;
+float autocvar_g_monster_mage_damageforcescale = 0.5;
float autocvar_g_monster_mage_attack_spike_damage;
float autocvar_g_monster_mage_attack_spike_radius;
float autocvar_g_monster_mage_attack_spike_delay;
float autocvar_g_monster_mage_speed_run;
float autocvar_g_monster_mage_speed_walk;
+/*
const float mage_anim_idle = 0;
const float mage_anim_walk = 1;
const float mage_anim_attack = 2;
const float mage_anim_pain = 3;
const float mage_anim_death = 4;
const float mage_anim_run = 5;
+*/
-void() mage_heal;
-void() mage_shield;
+void() M_Mage_Defend_Heal;
+void() M_Mage_Defend_Shield;
.entity mage_spike;
-.float shield_ltime;
+.float mage_shield_delay;
+.float mage_shield_time;
-float friend_needshelp(entity e)
+float M_Mage_Defend_Heal_Check(entity e)
{
if(e == world)
return false;
if(e.health <= 0)
return false;
- if(DIFF_TEAM(e, self) && e != self.monster_owner)
+ if(DIFF_TEAM(e, self) && e != self.monster_follow)
return false;
if(e.frozen)
return false;
return false;
}
-void mage_spike_explode()
+void M_Mage_Attack_Spike_Explode()
{
self.event_damage = func_null;
remove (self);
}
-void mage_spike_touch()
+void M_Mage_Attack_Spike_Touch()
{
PROJECTILE_TOUCH;
- mage_spike_explode();
+ M_Mage_Attack_Spike_Explode();
}
// copied from W_Seeker_Think
-void mage_spike_think()
+void M_Mage_Attack_Spike_Think()
{
entity e;
vector desireddir, olddir, newdir, eorg;
if (time > self.ltime || self.enemy.health <= 0 || self.owner.health <= 0)
{
self.projectiledeathtype |= HITTYPE_SPLASH;
- mage_spike_explode();
+ M_Mage_Attack_Spike_Explode();
}
spd = vlen(self.velocity);
UpdateCSQCProjectile(self);
}
-void mage_attack_spike()
+void M_Mage_Attack_Spike()
{
entity missile;
vector dir = normalize((self.enemy.origin + '0 0 10') - self.origin);
missile = spawn ();
missile.owner = missile.realowner = self;
- missile.think = mage_spike_think;
+ missile.think = M_Mage_Attack_Spike_Think;
missile.ltime = time + 7;
missile.nextthink = time;
missile.solid = SOLID_BBOX;
missile.velocity = dir * 400;
missile.avelocity = '300 300 300';
missile.enemy = self.enemy;
- missile.touch = mage_spike_touch;
+ missile.touch = M_Mage_Attack_Spike_Touch;
self.mage_spike = missile;
CSQCProjectile(missile, true, PROJECTILE_MAGE_SPIKE, true);
}
-void mage_heal()
+void M_Mage_Defend_Heal()
{
entity head;
float washealed = false;
- for(head = findradius(self.origin, (autocvar_g_monster_mage_heal_range)); head; head = head.chain) if(friend_needshelp(head))
+ for(head = findradius(self.origin, (autocvar_g_monster_mage_heal_range)); head; head = head.chain) if(M_Mage_Defend_Heal_Check(head))
{
washealed = true;
string fx = "";
{
pointparticles(particleeffectnum("healing_fx"), head.origin, '0 0 0', 1);
head.health = bound(0, head.health + (autocvar_g_monster_mage_heal_allies), head.max_health);
- if(!(head.spawnflags & MONSTERFLAG_INVINCIBLE))
+ if(!(head.spawnflags & MONSTERFLAG_INVINCIBLE) && head.sprite)
WaypointSprite_UpdateHealth(head.sprite, head.health);
}
}
if(washealed)
{
- self.frame = mage_anim_attack;
+ setanim(self, self.anim_shoot, true, true, true);
self.attack_finished_single = time + (autocvar_g_monster_mage_heal_delay);
+ self.anim_finished = time + 1.5;
}
}
-void mage_push()
+void M_Mage_Attack_Push()
{
sound(self, CH_SHOTS, "weapons/tagexp1.wav", 1, ATTEN_NORM);
RadiusDamage (self, self, (autocvar_g_monster_mage_attack_push_damage), (autocvar_g_monster_mage_attack_push_damage), (autocvar_g_monster_mage_attack_push_radius), world, world, (autocvar_g_monster_mage_attack_push_force), DEATH_MONSTER_MAGE, self.enemy);
pointparticles(particleeffectnum("TE_EXPLOSION"), self.origin, '0 0 0', 1);
- self.frame = mage_anim_attack;
+ setanim(self, self.anim_shoot, true, true, true);
self.attack_finished_single = time + (autocvar_g_monster_mage_attack_push_delay);
}
-void mage_teleport()
+void M_Mage_Attack_Teleport()
{
if(vlen(self.enemy.origin - self.origin) >= 500)
return;
self.attack_finished_single = time + 0.2;
}
-void mage_shield_remove()
+void M_Mage_Defend_Shield_Remove()
{
self.effects &= ~(EF_ADDITIVE | EF_BLUE);
- self.armorvalue = 0;
- self.m_armor_blockpercent = autocvar_g_monsters_armor_blockpercent;
+ self.armorvalue = autocvar_g_monsters_armor_blockpercent;
}
-void mage_shield()
+void M_Mage_Defend_Shield()
{
self.effects |= (EF_ADDITIVE | EF_BLUE);
- self.lastshielded = time + (autocvar_g_monster_mage_shield_delay);
- self.m_armor_blockpercent = (autocvar_g_monster_mage_shield_blockpercent);
- self.armorvalue = self.health;
- self.shield_ltime = time + (autocvar_g_monster_mage_shield_time);
- self.frame = mage_anim_attack;
+ self.mage_shield_delay = time + (autocvar_g_monster_mage_shield_delay);
+ self.armorvalue = (autocvar_g_monster_mage_shield_blockpercent);
+ self.mage_shield_time = time + (autocvar_g_monster_mage_shield_time);
+ setanim(self, self.anim_shoot, true, true, true);
self.attack_finished_single = time + 1;
+ self.anim_finished = time + 1;
}
-float mage_attack(float attack_type)
+float M_Mage_Attack(float attack_type)
{
switch(attack_type)
{
{
if(random() <= 0.7)
{
- mage_push();
+ M_Mage_Attack_Push();
return true;
}
{
if(random() <= 0.4)
{
- mage_teleport();
+ M_Mage_Attack_Teleport();
return true;
}
else
{
- self.frame = mage_anim_attack;
+ setanim(self, self.anim_shoot, true, true, true);
self.attack_finished_single = time + (autocvar_g_monster_mage_attack_spike_delay);
- defer(0.2, mage_attack_spike);
+ self.anim_finished = time + 1;
+ Monster_Delay(1, 0, 0.2, M_Mage_Attack_Spike);
return true;
}
}
return false;
}
-void spawnfunc_monster_mage()
-{
- self.classname = "monster_mage";
-
- if(!monster_initialize(MON_MAGE)) { remove(self); return; }
-}
+void spawnfunc_monster_mage() { Monster_Spawn(MON_MAGE); }
-// compatibility with old spawns
-void spawnfunc_monster_shalrath() { spawnfunc_monster_mage(); }
+#endif // SVQC
-float m_mage(float req)
+bool M_Mage(int req)
{
switch(req)
{
+ #ifdef SVQC
case MR_THINK:
{
entity head;
- float need_help = false;
+ bool need_help = false;
- for(head = findradius(self.origin, (autocvar_g_monster_mage_heal_range)); head; head = head.chain)
+ for(head = world; (head = findfloat(head, iscreature, true)); )
if(head != self)
- if(friend_needshelp(head))
+ if(vlen(head.origin - self.origin) <= (autocvar_g_monster_mage_heal_range))
+ if(M_Mage_Defend_Heal_Check(head))
{
need_help = true;
break;
if(self.health < (autocvar_g_monster_mage_heal_minhealth) || need_help)
if(time >= self.attack_finished_single)
if(random() < 0.5)
- mage_heal();
+ M_Mage_Defend_Heal();
- if(time >= self.shield_ltime && self.armorvalue)
- mage_shield_remove();
+ if(time >= self.mage_shield_time && self.armorvalue)
+ M_Mage_Defend_Shield_Remove();
if(self.enemy)
if(self.health < self.max_health)
- if(time >= self.lastshielded)
+ if(time >= self.mage_shield_delay)
if(random() < 0.5)
- mage_shield();
+ M_Mage_Defend_Shield();
- monster_move((autocvar_g_monster_mage_speed_run), (autocvar_g_monster_mage_speed_walk), (autocvar_g_monster_mage_speed_stop), mage_anim_walk, mage_anim_run, mage_anim_idle);
+ return true;
+ }
+ case MR_PAIN:
+ {
return true;
}
case MR_DEATH:
{
- self.frame = mage_anim_death;
+ setanim(self, self.anim_die1, false, true, true);
+ return true;
+ }
+ #endif
+ #ifndef MENUQC
+ case MR_ANIM:
+ {
+ vector none = '0 0 0';
+ self.anim_die1 = animfixfps(self, '4 1 0.5', none); // 2 seconds
+ self.anim_walk = animfixfps(self, '1 1 1', none);
+ self.anim_idle = animfixfps(self, '0 1 1', none);
+ self.anim_pain1 = animfixfps(self, '3 1 2', none); // 0.5 seconds
+ self.anim_shoot = animfixfps(self, '2 1 5', none); // analyze models and set framerate
+ self.anim_run = animfixfps(self, '5 1 1', none);
+
return true;
}
+ #endif
+ #ifdef SVQC
case MR_SETUP:
{
if(!self.health) self.health = (autocvar_g_monster_mage_health);
+ if(!self.speed) { self.speed = (autocvar_g_monster_mage_speed_walk); }
+ if(!self.speed2) { self.speed2 = (autocvar_g_monster_mage_speed_run); }
+ if(!self.stopspeed) { self.stopspeed = (autocvar_g_monster_mage_speed_stop); }
+ if(!self.damageforcescale) { self.damageforcescale = (autocvar_g_monster_mage_damageforcescale); }
self.monster_loot = spawnfunc_item_health_large;
- self.monster_attackfunc = mage_attack;
- self.frame = mage_anim_walk;
return true;
}
precache_sound ("weapons/tagexp1.wav");
return true;
}
+ #endif
}
return true;
}
-#endif // SVQC
-#ifdef CSQC
-float m_mage(float req)
-{
- switch(req)
- {
- case MR_PRECACHE:
- {
- return true;
- }
- }
-
- return true;
-}
-
-#endif // CSQC
#endif // REGISTER_MONSTER
#ifdef REGISTER_MONSTER
REGISTER_MONSTER(
/* MON_##id */ SHAMBLER,
-/* function */ m_shambler,
+/* functions */ M_Shambler, M_Shambler_Attack,
/* spawnflags */ MONSTER_SIZE_BROKEN | MON_FLAG_SUPERMONSTER | MON_FLAG_MELEE | MON_FLAG_RANGED,
/* mins,maxs */ '-41 -41 -31', '41 41 65',
/* model */ "shambler.mdl",
#else
#ifdef SVQC
float autocvar_g_monster_shambler_health;
+float autocvar_g_monster_shambler_damageforcescale = 0.1;
float autocvar_g_monster_shambler_attack_smash_damage;
+float autocvar_g_monster_shambler_attack_smash_range;
float autocvar_g_monster_shambler_attack_claw_damage;
float autocvar_g_monster_shambler_attack_lightning_damage;
+float autocvar_g_monster_shambler_attack_lightning_damage_zap = 15;
float autocvar_g_monster_shambler_attack_lightning_force;
float autocvar_g_monster_shambler_attack_lightning_radius;
float autocvar_g_monster_shambler_attack_lightning_radius_zap;
float autocvar_g_monster_shambler_speed_run;
float autocvar_g_monster_shambler_speed_walk;
+/*
const float shambler_anim_stand = 0;
const float shambler_anim_walk = 1;
const float shambler_anim_run = 2;
const float shambler_anim_magic = 6;
const float shambler_anim_pain = 7;
const float shambler_anim_death = 8;
+*/
.float shambler_lastattack; // delay attacks separately
-void shambler_smash()
+void M_Shambler_Attack_Smash()
{
makevectors(self.angles);
pointparticles(particleeffectnum("explosion_medium"), (self.origin + (v_forward * 150)) - ('0 0 1' * self.maxs.z), '0 0 0', 1);
sound(self, CH_SHOTS, "weapons/rocket_impact.wav", VOL_BASE, ATTEN_NORM);
- tracebox(self.origin + v_forward * 50, self.mins * 0.5, self.maxs * 0.5, self.origin + v_forward * 500, MOVE_NORMAL, self);
+ // RadiusDamage does NOT support custom starting location, which means we must use this hack...
+
+ tracebox(self.origin + v_forward * 50, self.mins * 0.5, self.maxs * 0.5, self.origin + v_forward * autocvar_g_monster_shambler_attack_smash_range, MOVE_NORMAL, self);
if(trace_ent.takedamage)
- Damage(trace_ent, self, self, (autocvar_g_monster_shambler_attack_smash_damage) * Monster_SkillModifier(), DEATH_MONSTER_SHAMBLER_SMASH, trace_ent.origin, normalize(trace_ent.origin - self.origin));
+ Damage(trace_ent, self, self, (autocvar_g_monster_shambler_attack_smash_damage) * MONSTER_SKILLMOD(self), DEATH_MONSTER_SHAMBLER_SMASH, trace_ent.origin, normalize(trace_ent.origin - self.origin));
}
-void shambler_swing()
+void M_Shambler_Attack_Swing()
{
float r = (random() < 0.5);
- monster_melee(self.enemy, (autocvar_g_monster_shambler_attack_claw_damage), ((r) ? shambler_anim_swingr : shambler_anim_swingl), self.attack_range, 0.8, DEATH_MONSTER_SHAMBLER_CLAW, true);
- if(r)
+ if(r && Monster_Attack_Melee(self.enemy, (autocvar_g_monster_shambler_attack_claw_damage), ((r) ? self.anim_melee2 : self.anim_melee3), self.attack_range, 0.8, DEATH_MONSTER_SHAMBLER_CLAW, true))
{
- defer(0.5, shambler_swing);
+ Monster_Delay(1, 0, 0.5, M_Shambler_Attack_Swing);
self.attack_finished_single += 0.5;
+ self.anim_finished = self.attack_finished_single;
}
}
-void shambler_lightning_explode()
+void M_Shambler_Attack_Lightning_Explode()
{
entity head;
for(head = findradius(self.origin, (autocvar_g_monster_shambler_attack_lightning_radius_zap)); head; head = head.chain) if(head != self.realowner) if(head.takedamage)
{
te_csqc_lightningarc(self.origin, head.origin);
- Damage(head, self, self.realowner, (autocvar_g_monster_shambler_attack_lightning_damage) * Monster_SkillModifier(), DEATH_MONSTER_SHAMBLER_ZAP, head.origin, '0 0 0');
+ Damage(head, self, self.realowner, (autocvar_g_monster_shambler_attack_lightning_damage_zap) * MONSTER_SKILLMOD(self), DEATH_MONSTER_SHAMBLER_ZAP, head.origin, '0 0 0');
}
self.think = SUB_Remove;
self.nextthink = time + 0.2;
}
-void shambler_lightning_damage(entity inflictor, entity attacker, float damage, int deathtype, vector hitloc, vector force)
+void M_Shambler_Attack_Lightning_Damage(entity inflictor, entity attacker, float damage, int deathtype, vector hitloc, vector force)
{
if (self.health <= 0)
return;
W_PrepareExplosionByDamage(attacker, self.use);
}
-void shambler_lightning_touch()
+void M_Shambler_Attack_Lightning_Touch()
{
PROJECTILE_TOUCH;
self.use ();
}
-void shambler_lightning_think()
+void M_Shambler_Attack_Lightning_Think()
{
self.nextthink = time;
if (time > self.cnt)
{
other = world;
- shambler_lightning_explode();
+ M_Shambler_Attack_Lightning_Explode();
return;
}
}
-void shambler_lightning()
+void M_Shambler_Attack_Lightning()
{
entity gren;
gren.cnt = time + 5;
gren.nextthink = time;
- gren.think = shambler_lightning_think;
- gren.use = shambler_lightning_explode;
- gren.touch = shambler_lightning_touch;
+ gren.think = M_Shambler_Attack_Lightning_Think;
+ gren.use = M_Shambler_Attack_Lightning_Explode;
+ gren.touch = M_Shambler_Attack_Lightning_Touch;
gren.takedamage = DAMAGE_YES;
gren.health = 50;
gren.damageforcescale = 0;
- gren.event_damage = shambler_lightning_damage;
+ gren.event_damage = M_Shambler_Attack_Lightning_Damage;
gren.damagedbycontents = true;
gren.missile_flags = MIF_SPLASH | MIF_ARC;
W_SetupProjVelocity_Explicit(gren, v_forward, v_up, (autocvar_g_monster_shambler_attack_lightning_speed), (autocvar_g_monster_shambler_attack_lightning_speed_up), 0, 0, false);
CSQCProjectile(gren, true, PROJECTILE_SHAMBLER_LIGHTNING, true);
}
-float shambler_attack(float attack_type)
+float M_Shambler_Attack(float attack_type)
{
switch(attack_type)
{
case MONSTER_ATTACK_MELEE:
{
- shambler_swing();
+ M_Shambler_Attack_Swing();
return true;
}
case MONSTER_ATTACK_RANGED:
{
+ float randomness = random(), enemy_len = vlen(self.enemy.origin - self.origin);
+
if(time >= self.shambler_lastattack) // shambler doesn't attack much
if(self.flags & FL_ONGROUND)
- if(random() <= 0.5 && vlen(self.enemy.origin - self.origin) <= 500)
+ if(randomness <= 0.5 && enemy_len <= autocvar_g_monster_shambler_attack_smash_range)
{
- self.frame = shambler_anim_smash;
- defer(0.7, shambler_smash);
+ setanim(self, self.anim_melee2, true, true, false);
+ Monster_Delay(1, 0, 0.7, M_Shambler_Attack_Smash);
self.attack_finished_single = time + 1.1;
- self.shambler_lastattack = time + 3;
+ self.anim_finished = time + 1.1;
+ self.state = MONSTER_ATTACK_MELEE; // kinda a melee attack
+ self.shambler_lastattack = time + 3 + random() * 1.5;
return true;
}
- else if(random() <= 0.1) // small chance, don't want this spammed
+ else if(randomness <= 0.1 && enemy_len >= autocvar_g_monster_shambler_attack_smash_range * 1.5) // small chance, don't want this spammed
{
- self.frame = shambler_anim_magic;
+ setanim(self, self.anim_shoot, true, true, false);
+ self.state = MONSTER_ATTACK_MELEE; // maybe we should rename this to something more general
self.attack_finished_single = time + 1.1;
- self.shambler_lastattack = time + 3;
- defer(0.6, shambler_lightning);
+ self.anim_finished = 1.1;
+ self.shambler_lastattack = time + 3 + random() * 1.5;
+ Monster_Delay(1, 0, 0.6, M_Shambler_Attack_Lightning);
return true;
}
return false;
}
-void spawnfunc_monster_shambler()
-{
- self.classname = "monster_shambler";
-
- if(!monster_initialize(MON_SHAMBLER)) { remove(self); return; }
-}
+void spawnfunc_monster_shambler() { Monster_Spawn(MON_SHAMBLER); }
+#endif // SVQC
-float m_shambler(float req)
+bool M_Shambler(int req)
{
switch(req)
{
+ #ifdef SVQC
case MR_THINK:
{
- monster_move((autocvar_g_monster_shambler_speed_run), (autocvar_g_monster_shambler_speed_walk), (autocvar_g_monster_shambler_speed_stop), shambler_anim_run, shambler_anim_walk, shambler_anim_stand);
+ return true;
+ }
+ case MR_PAIN:
+ {
+ self.pain_finished = time + 0.5;
+ setanim(self, self.anim_pain1, true, true, false);
return true;
}
case MR_DEATH:
{
- self.frame = shambler_anim_death;
+ setanim(self, self.anim_die1, false, true, true);
return true;
}
+ #endif
+ #ifndef MENUQC
+ case MR_ANIM:
+ {
+ vector none = '0 0 0';
+ self.anim_die1 = animfixfps(self, '8 1 0.5', none); // 2 seconds
+ self.anim_walk = animfixfps(self, '1 1 1', none);
+ self.anim_idle = animfixfps(self, '0 1 1', none);
+ self.anim_pain1 = animfixfps(self, '7 1 2', none); // 0.5 seconds
+ self.anim_melee1 = animfixfps(self, '3 1 5', none); // analyze models and set framerate
+ self.anim_melee2 = animfixfps(self, '4 1 5', none); // analyze models and set framerate
+ self.anim_melee3 = animfixfps(self, '5 1 5', none); // analyze models and set framerate
+ self.anim_shoot = animfixfps(self, '6 1 5', none); // analyze models and set framerate
+ self.anim_run = animfixfps(self, '2 1 1', none);
+
+ return true;
+ }
+ #endif
+ #ifdef SVQC
case MR_SETUP:
{
if(!self.health) self.health = (autocvar_g_monster_shambler_health);
if(!self.attack_range) self.attack_range = 150;
+ if(!self.speed) { self.speed = (autocvar_g_monster_shambler_speed_walk); }
+ if(!self.speed2) { self.speed2 = (autocvar_g_monster_shambler_speed_run); }
+ if(!self.stopspeed) { self.stopspeed = (autocvar_g_monster_shambler_speed_stop); }
+ if(!self.damageforcescale) { self.damageforcescale = (autocvar_g_monster_shambler_damageforcescale); }
self.monster_loot = spawnfunc_item_health_mega;
- self.monster_attackfunc = shambler_attack;
- self.frame = shambler_anim_stand;
- self.weapon = WEP_VORTEX;
+ self.weapon = WEP_ELECTRO; // matches attacks better than WEP_VORTEX
+
+ setanim(self, self.anim_shoot, false, true, true);
+ self.spawn_time = self.animstate_endtime;
+ self.spawnshieldtime = self.spawn_time;
return true;
}
precache_model("models/monsters/shambler.mdl");
return true;
}
+ #endif
}
return true;
}
-#endif // SVQC
-#ifdef CSQC
-float m_shambler(float req)
-{
- switch(req)
- {
- case MR_PRECACHE:
- {
- return true;
- }
- }
-
- return true;
-}
-
-#endif // CSQC
#endif // REGISTER_MONSTER
#ifdef REGISTER_MONSTER
REGISTER_MONSTER(
/* MON_##id */ SPIDER,
-/* function */ m_spider,
+/* functions */ M_Spider, M_Spider_Attack,
/* spawnflags */ MON_FLAG_MELEE | MON_FLAG_RANGED,
/* mins,maxs */ '-18 -18 -25', '18 18 30',
/* model */ "spider.dpm",
#else
#ifdef SVQC
float autocvar_g_monster_spider_health;
+float autocvar_g_monster_spider_damageforcescale = 0.6;
float autocvar_g_monster_spider_attack_bite_damage;
float autocvar_g_monster_spider_attack_bite_delay;
float autocvar_g_monster_spider_attack_web_damagetime;
float autocvar_g_monster_spider_speed_run;
float autocvar_g_monster_spider_speed_walk;
+/*
const float spider_anim_idle = 0;
const float spider_anim_walk = 1;
const float spider_anim_attack = 2;
const float spider_anim_attack2 = 3;
+*/
.float spider_web_delay;
-void spider_web_explode()
+void M_Spider_Attack_Web_Explode()
{
entity e;
if(self)
pointparticles(particleeffectnum("electro_impact"), self.origin, '0 0 0', 1);
RadiusDamage(self, self.realowner, 0, 0, 25, world, world, 25, self.projectiledeathtype, world);
- for(e = findradius(self.origin, 25); e; e = e.chain) if(e != self) if(e.takedamage && e.deadflag == DEAD_NO) if(e.health > 0) if(e.monsterid != MON_SPIDER)
+ for(e = findradius(self.origin, 25); e; e = e.chain) if(e != self) if(e.takedamage && e.deadflag == DEAD_NO) if(e.health > 0) if(e.monsterid != self.realowner.monsterid)
e.spider_slowness = time + (autocvar_g_monster_spider_attack_web_damagetime);
remove(self);
}
}
-void spider_web_touch()
+void M_Spider_Attack_Web_Touch()
{
PROJECTILE_TOUCH;
- spider_web_explode();
+ M_Spider_Attack_Web_Explode();
}
-void spider_shootweb()
+void M_Spider_Attack_Web()
{
monster_makevectors(self.enemy);
entity proj = spawn ();
proj.classname = "plasma";
proj.owner = proj.realowner = self;
- proj.use = spider_web_touch;
+ proj.use = M_Spider_Attack_Web_Explode;
proj.think = adaptor_think2use_hittype_splash;
proj.bot_dodge = true;
proj.bot_dodgerating = 0;
//proj.glow_color = 45;
proj.movetype = MOVETYPE_BOUNCE;
W_SetupProjVelocity_Explicit(proj, v_forward, v_up, (autocvar_g_monster_spider_attack_web_speed), (autocvar_g_monster_spider_attack_web_speed_up), 0, 0, false);
- proj.touch = spider_web_touch;
+ proj.touch = M_Spider_Attack_Web_Touch;
setsize(proj, '-4 -4 -4', '4 4 4');
proj.takedamage = DAMAGE_NO;
proj.damageforcescale = 0;
CSQCProjectile(proj, true, PROJECTILE_ELECTRO, true);
}
-float spider_attack(float attack_type)
+float M_Spider_Attack(float attack_type)
{
switch(attack_type)
{
case MONSTER_ATTACK_MELEE:
{
- return monster_melee(self.enemy, (autocvar_g_monster_spider_attack_bite_damage), ((random() > 0.5) ? spider_anim_attack : spider_anim_attack2), self.attack_range, (autocvar_g_monster_spider_attack_bite_delay), DEATH_MONSTER_SPIDER, true);
+ return Monster_Attack_Melee(self.enemy, (autocvar_g_monster_spider_attack_bite_damage), ((random() > 0.5) ? self.anim_melee : self.anim_shoot), self.attack_range, (autocvar_g_monster_spider_attack_bite_delay), DEATH_MONSTER_SPIDER, true);
}
case MONSTER_ATTACK_RANGED:
{
if(time >= self.spider_web_delay)
{
- self.frame = spider_anim_attack2;
+ setanim(self, self.anim_shoot, true, true, true);
self.attack_finished_single = time + (autocvar_g_monster_spider_attack_web_delay);
- spider_shootweb();
+ self.anim_finished = time + 1;
+ M_Spider_Attack_Web();
self.spider_web_delay = time + 3;
return true;
}
return false;
}
-void spawnfunc_monster_spider()
-{
- self.classname = "monster_spider";
-
- if(!monster_initialize(MON_SPIDER)) { remove(self); return; }
-}
+void spawnfunc_monster_spider() { Monster_Spawn(MON_SPIDER); }
+#endif // SVQC
-float m_spider(float req)
+bool M_Spider(int req)
{
switch(req)
{
+ #ifdef SVQC
case MR_THINK:
{
- monster_move((autocvar_g_monster_spider_speed_run), (autocvar_g_monster_spider_speed_walk), (autocvar_g_monster_spider_speed_stop), spider_anim_walk, spider_anim_walk, spider_anim_idle);
+ return true;
+ }
+ case MR_PAIN:
+ {
return true;
}
case MR_DEATH:
{
- self.frame = spider_anim_attack;
+ setanim(self, self.anim_melee, false, true, true);
self.angles_x = 180;
return true;
}
+ #endif
+ #ifndef MENUQC
+ case MR_ANIM:
+ {
+ vector none = '0 0 0';
+ self.anim_walk = animfixfps(self, '1 1 1', none);
+ self.anim_idle = animfixfps(self, '0 1 1', none);
+ self.anim_melee = animfixfps(self, '2 1 5', none); // analyze models and set framerate
+ self.anim_shoot = animfixfps(self, '3 1 5', none); // analyze models and set framerate
+ self.anim_run = animfixfps(self, '1 1 1', none);
+
+ return true;
+ }
+ #endif
+ #ifdef SVQC
case MR_SETUP:
{
if(!self.health) self.health = (autocvar_g_monster_spider_health);
+ if(!self.speed) { self.speed = (autocvar_g_monster_spider_speed_walk); }
+ if(!self.speed2) { self.speed2 = (autocvar_g_monster_spider_speed_run); }
+ if(!self.stopspeed) { self.stopspeed = (autocvar_g_monster_spider_speed_stop); }
+ if(!self.damageforcescale) { self.damageforcescale = (autocvar_g_monster_spider_damageforcescale); }
self.monster_loot = spawnfunc_item_health_medium;
- self.monster_attackfunc = spider_attack;
- self.frame = spider_anim_idle;
return true;
}
precache_sound ("weapons/electro_fire2.wav");
return true;
}
+ #endif
}
return true;
}
-#endif // SVQC
-#ifdef CSQC
-float m_spider(float req)
-{
- switch(req)
- {
- case MR_PRECACHE:
- {
- return true;
- }
- }
-
- return true;
-}
-
-#endif // CSQC
#endif // REGISTER_MONSTER
#ifdef REGISTER_MONSTER
REGISTER_MONSTER(
/* MON_##id */ WYVERN,
-/* function */ m_wyvern,
+/* functions */ M_Wyvern, M_Wyvern_Attack,
/* spawnflags */ MONSTER_TYPE_FLY | MONSTER_SIZE_BROKEN | MON_FLAG_RANGED,
/* mins,maxs */ '-20 -20 -58', '20 20 20',
/* model */ "wizard.mdl",
#else
#ifdef SVQC
float autocvar_g_monster_wyvern_health;
+float autocvar_g_monster_wyvern_damageforcescale = 0.6;
float autocvar_g_monster_wyvern_attack_fireball_damage;
float autocvar_g_monster_wyvern_attack_fireball_edgedamage;
float autocvar_g_monster_wyvern_attack_fireball_damagetime;
float autocvar_g_monster_wyvern_speed_run;
float autocvar_g_monster_wyvern_speed_walk;
+/*
const float wyvern_anim_hover = 0;
const float wyvern_anim_fly = 1;
const float wyvern_anim_magic = 2;
const float wyvern_anim_pain = 3;
const float wyvern_anim_death = 4;
+*/
-void wyvern_fireball_explode()
+void M_Wyvern_Attack_Fireball_Explode()
{
entity e;
if(self)
RadiusDamage(self, self.realowner, (autocvar_g_monster_wyvern_attack_fireball_damage), (autocvar_g_monster_wyvern_attack_fireball_edgedamage), (autocvar_g_monster_wyvern_attack_fireball_force), world, world, (autocvar_g_monster_wyvern_attack_fireball_radius), self.projectiledeathtype, world);
for(e = world; (e = findfloat(e, takedamage, DAMAGE_AIM)); ) if(vlen(e.origin - self.origin) <= (autocvar_g_monster_wyvern_attack_fireball_radius))
- Fire_AddDamage(e, self, 5 * Monster_SkillModifier(), (autocvar_g_monster_wyvern_attack_fireball_damagetime), self.projectiledeathtype);
+ Fire_AddDamage(e, self, 5 * MONSTER_SKILLMOD(self), (autocvar_g_monster_wyvern_attack_fireball_damagetime), self.projectiledeathtype);
remove(self);
}
}
-void wyvern_fireball_touch()
+void M_Wyvern_Attack_Fireball_Touch()
{
PROJECTILE_TOUCH;
- wyvern_fireball_explode();
+ M_Wyvern_Attack_Fireball_Explode();
}
-void wyvern_fireball()
+void M_Wyvern_Attack_Fireball()
{
entity missile = spawn();
vector dir = normalize((self.enemy.origin + '0 0 10') - self.origin);
missile.velocity = dir * (autocvar_g_monster_wyvern_attack_fireball_speed);
missile.avelocity = '300 300 300';
missile.nextthink = time + 5;
- missile.think = wyvern_fireball_explode;
+ missile.think = M_Wyvern_Attack_Fireball_Explode;
missile.enemy = self.enemy;
- missile.touch = wyvern_fireball_touch;
+ missile.touch = M_Wyvern_Attack_Fireball_Touch;
CSQCProjectile(missile, true, PROJECTILE_FIREMINE, true);
}
-float wyvern_attack(float attack_type)
+float M_Wyvern_Attack(float attack_type)
{
switch(attack_type)
{
case MONSTER_ATTACK_RANGED:
{
self.attack_finished_single = time + 1.2;
+ self.anim_finished = time + 1.2;
- wyvern_fireball();
+ M_Wyvern_Attack_Fireball();
return true;
}
return false;
}
-void spawnfunc_monster_wyvern()
-{
- self.classname = "monster_wyvern";
-
- if(!monster_initialize(MON_WYVERN)) { remove(self); return; }
-}
+void spawnfunc_monster_wyvern() { Monster_Spawn(MON_WYVERN); }
-// compatibility with old spawns
-void spawnfunc_monster_wizard() { spawnfunc_monster_wyvern(); }
+#endif // SVQC
-float m_wyvern(float req)
+bool M_Wyvern(int req)
{
switch(req)
{
+ #ifdef SVQC
case MR_THINK:
{
- monster_move((autocvar_g_monster_wyvern_speed_run), (autocvar_g_monster_wyvern_speed_walk), (autocvar_g_monster_wyvern_speed_stop), wyvern_anim_fly, wyvern_anim_hover, wyvern_anim_hover);
+ return true;
+ }
+ case MR_PAIN:
+ {
+ self.pain_finished = time + 0.5;
+ setanim(self, self.anim_pain1, true, true, false);
return true;
}
case MR_DEATH:
{
- self.frame = wyvern_anim_death;
+ setanim(self, self.anim_die1, false, true, true);
self.velocity_x = -200 + 400 * random();
self.velocity_y = -200 + 400 * random();
self.velocity_z = 100 + 100 * random();
return true;
}
+ #endif
+ #ifndef MENUQC
+ case MR_ANIM:
+ {
+ vector none = '0 0 0';
+ self.anim_die1 = animfixfps(self, '4 1 0.5', none); // 2 seconds
+ self.anim_walk = animfixfps(self, '1 1 1', none);
+ self.anim_idle = animfixfps(self, '0 1 1', none);
+ self.anim_pain1 = animfixfps(self, '3 1 2', none); // 0.5 seconds
+ self.anim_shoot = animfixfps(self, '2 1 5', none); // analyze models and set framerate
+ self.anim_run = animfixfps(self, '1 1 1', none);
+
+ return true;
+ }
+ #endif
+ #ifdef SVQC
case MR_SETUP:
{
if(!self.health) self.health = (autocvar_g_monster_wyvern_health);
+ if(!self.speed) { self.speed = (autocvar_g_monster_wyvern_speed_walk); }
+ if(!self.speed2) { self.speed2 = (autocvar_g_monster_wyvern_speed_run); }
+ if(!self.stopspeed) { self.stopspeed = (autocvar_g_monster_wyvern_speed_stop); }
+ if(!self.damageforcescale) { self.damageforcescale = (autocvar_g_monster_wyvern_damageforcescale); }
self.monster_loot = spawnfunc_item_cells;
- self.monster_attackfunc = wyvern_attack;
- self.frame = wyvern_anim_hover;
return true;
}
precache_model("models/monsters/wizard.mdl");
return true;
}
+ #endif
}
return true;
}
-#endif // SVQC
-#ifdef CSQC
-float m_wyvern(float req)
-{
- switch(req)
- {
- case MR_PRECACHE:
- {
- return true;
- }
- }
-
- return true;
-}
-
-#endif // CSQC
#endif // REGISTER_MONSTER
#ifdef REGISTER_MONSTER
REGISTER_MONSTER(
/* MON_##id */ ZOMBIE,
-/* function */ m_zombie,
+/* functions */ M_Zombie, M_Zombie_Attack,
/* spawnflags */ MON_FLAG_MELEE,
/* mins,maxs */ '-18 -18 -25', '18 18 47',
/* model */ "zombie.dpm",
#else
#ifdef SVQC
float autocvar_g_monster_zombie_health;
+float autocvar_g_monster_zombie_damageforcescale = 0.55;
float autocvar_g_monster_zombie_attack_melee_damage;
float autocvar_g_monster_zombie_attack_melee_delay;
float autocvar_g_monster_zombie_attack_leap_damage;
float autocvar_g_monster_zombie_speed_run;
float autocvar_g_monster_zombie_speed_walk;
+/*
const float zombie_anim_attackleap = 0;
const float zombie_anim_attackrun1 = 1;
const float zombie_anim_attackrun2 = 2;
const float zombie_anim_runforwardleft = 28;
const float zombie_anim_runforwardright = 29;
const float zombie_anim_spawn = 30;
+*/
-void zombie_attack_leap_touch()
+void M_Zombie_Attack_Leap_Touch()
{
if (self.health <= 0)
return;
{
angles_face = vectoangles(self.moveto - self.origin);
angles_face = normalize(angles_face) * (autocvar_g_monster_zombie_attack_leap_force);
- Damage(other, self, self, (autocvar_g_monster_zombie_attack_leap_damage) * Monster_SkillModifier(), DEATH_MONSTER_ZOMBIE_JUMP, other.origin, angles_face);
- self.touch = MonsterTouch; // instantly turn it off to stop damage spam
+ Damage(other, self, self, (autocvar_g_monster_zombie_attack_leap_damage) * MONSTER_SKILLMOD(self), DEATH_MONSTER_ZOMBIE_JUMP, other.origin, angles_face);
+ self.touch = Monster_Touch; // instantly turn it off to stop damage spam
+ self.state = 0;
}
if (trace_dphitcontents)
- self.touch = MonsterTouch;
+ {
+ self.state = 0;
+ self.touch = Monster_Touch;
+ }
}
-void zombie_blockend()
+void M_Zombie_Defend_Block_End()
{
if(self.health <= 0)
return;
- self.frame = zombie_anim_blockend;
- self.armorvalue = 0;
- self.m_armor_blockpercent = autocvar_g_monsters_armor_blockpercent;
+ setanim(self, self.anim_blockend, false, true, true);
+ self.armorvalue = autocvar_g_monsters_armor_blockpercent;
}
-float zombie_block()
+float M_Zombie_Defend_Block()
{
- self.frame = zombie_anim_blockstart;
- self.armorvalue = 100;
- self.m_armor_blockpercent = 0.9;
- self.state = MONSTER_STATE_ATTACK_MELEE; // freeze monster
+ self.armorvalue = 0.9;
+ self.state = MONSTER_ATTACK_MELEE; // freeze monster
self.attack_finished_single = time + 2.1;
+ self.anim_finished = self.attack_finished_single;
+ setanim(self, self.anim_blockstart, false, true, true);
- defer(2, zombie_blockend);
+ Monster_Delay(1, 0, 2, M_Zombie_Defend_Block_End);
return true;
}
-float zombie_attack(float attack_type)
+float M_Zombie_Attack(float attack_type)
{
switch(attack_type)
{
case MONSTER_ATTACK_MELEE:
{
- float rand = random(), chosen_anim;
+ if(random() < 0.3 && self.health < 75 && self.enemy.health > 10)
+ return M_Zombie_Defend_Block();
+
+ float rand = random();
+ vector chosen_anim;
if(rand < 0.33)
- chosen_anim = zombie_anim_attackstanding1;
+ chosen_anim = self.anim_melee1;
else if(rand < 0.66)
- chosen_anim = zombie_anim_attackstanding2;
+ chosen_anim = self.anim_melee2;
else
- chosen_anim = zombie_anim_attackstanding3;
+ chosen_anim = self.anim_melee3;
- if(random() < 0.3 && self.health < 75 && self.enemy.health > 10)
- return zombie_block();
-
- return monster_melee(self.enemy, (autocvar_g_monster_zombie_attack_melee_damage), chosen_anim, self.attack_range, (autocvar_g_monster_zombie_attack_melee_delay), DEATH_MONSTER_ZOMBIE_MELEE, true);
+ return Monster_Attack_Melee(self.enemy, (autocvar_g_monster_zombie_attack_melee_damage), chosen_anim, self.attack_range, (autocvar_g_monster_zombie_attack_melee_delay), DEATH_MONSTER_ZOMBIE_MELEE, true);
}
case MONSTER_ATTACK_RANGED:
{
makevectors(self.angles);
- return monster_leap(zombie_anim_attackleap, zombie_attack_leap_touch, v_forward * (autocvar_g_monster_zombie_attack_leap_speed) + '0 0 200', (autocvar_g_monster_zombie_attack_leap_delay));
+ return Monster_Attack_Leap(self.anim_shoot, M_Zombie_Attack_Leap_Touch, v_forward * (autocvar_g_monster_zombie_attack_leap_speed) + '0 0 200', (autocvar_g_monster_zombie_attack_leap_delay));
}
}
return false;
}
-void spawnfunc_monster_zombie()
-{
- self.classname = "monster_zombie";
-
- if(!monster_initialize(MON_ZOMBIE)) { remove(self); return; }
-}
+void spawnfunc_monster_zombie() { Monster_Spawn(MON_ZOMBIE); }
+#endif // SVQC
-float m_zombie(float req)
+bool M_Zombie(int req)
{
switch(req)
{
+ #ifdef SVQC
case MR_THINK:
{
- monster_move((autocvar_g_monster_zombie_speed_run), (autocvar_g_monster_zombie_speed_walk), (autocvar_g_monster_zombie_speed_stop), zombie_anim_runforward, zombie_anim_runforward, zombie_anim_idle);
+ if(time >= self.spawn_time)
+ self.damageforcescale = autocvar_g_monster_zombie_damageforcescale;
+ return true;
+ }
+ case MR_PAIN:
+ {
+ self.pain_finished = time + 0.34;
+ setanim(self, ((random() > 0.5) ? self.anim_pain1 : self.anim_pain2), true, true, false);
return true;
}
case MR_DEATH:
{
- self.armorvalue = 0;
- self.m_armor_blockpercent = autocvar_g_monsters_armor_blockpercent;
- self.frame = ((random() > 0.5) ? zombie_anim_deathback1 : zombie_anim_deathfront1);
+ self.armorvalue = autocvar_g_monsters_armor_blockpercent;
+
+ setanim(self, ((random() > 0.5) ? self.anim_die1 : self.anim_die2), false, true, true);
+ return true;
+ }
+ #endif
+ #ifndef MENUQC
+ case MR_ANIM:
+ {
+ vector none = '0 0 0';
+ self.anim_die1 = animfixfps(self, '9 1 0.5', none); // 2 seconds
+ self.anim_die2 = animfixfps(self, '12 1 0.5', none); // 2 seconds
+ self.anim_spawn = animfixfps(self, '30 1 3', none);
+ self.anim_walk = animfixfps(self, '27 1 1', none);
+ self.anim_idle = animfixfps(self, '19 1 1', none);
+ self.anim_pain1 = animfixfps(self, '20 1 2', none); // 0.5 seconds
+ self.anim_pain2 = animfixfps(self, '22 1 2', none); // 0.5 seconds
+ self.anim_melee1 = animfixfps(self, '4 1 5', none); // analyze models and set framerate
+ self.anim_melee2 = animfixfps(self, '4 1 5', none); // analyze models and set framerate
+ self.anim_melee3 = animfixfps(self, '4 1 5', none); // analyze models and set framerate
+ self.anim_shoot = animfixfps(self, '0 1 5', none); // analyze models and set framerate
+ self.anim_run = animfixfps(self, '27 1 1', none);
+ self.anim_blockstart = animfixfps(self, '8 1 1', none);
+ self.anim_blockend = animfixfps(self, '7 1 1', none);
+
return true;
}
+ #endif
+ #ifdef SVQC
case MR_SETUP:
{
if(!self.health) self.health = (autocvar_g_monster_zombie_health);
+ if(!self.speed) { self.speed = (autocvar_g_monster_zombie_speed_walk); }
+ if(!self.speed2) { self.speed2 = (autocvar_g_monster_zombie_speed_run); }
+ if(!self.stopspeed) { self.stopspeed = (autocvar_g_monster_zombie_speed_stop); }
if(self.spawnflags & MONSTERFLAG_NORESPAWN)
self.spawnflags &= ~MONSTERFLAG_NORESPAWN; // zombies always respawn
self.spawnflags |= MONSTER_RESPAWN_DEATHPOINT;
self.monster_loot = spawnfunc_item_health_medium;
- self.monster_attackfunc = zombie_attack;
- self.frame = zombie_anim_spawn;
- self.spawn_time = time + 2.1;
self.spawnshieldtime = self.spawn_time;
self.respawntime = 0.2;
+ self.damageforcescale = 0.0001; // no push while spawning
+
+ setanim(self, self.anim_spawn, false, true, true);
+ self.spawn_time = self.animstate_endtime;
return true;
}
precache_model("models/monsters/zombie.dpm");
return true;
}
+ #endif
}
return true;
}
-#endif // SVQC
-#ifdef CSQC
-float m_zombie(float req)
-{
- switch(req)
- {
- case MR_PRECACHE:
- {
- return true;
- }
- }
-
- return true;
-}
-
-#endif // CSQC
#endif // REGISTER_MONSTER
#endif
entity spawnmonster (string monster, float monster_id, entity spawnedby, entity own, vector orig, float respwn, float invincible, float moveflag)
{
- // ensure spawnfunc database is initialized
- //initialize_field_db();
-
+ float i;
entity e = spawn();
- float i;
e.spawnflags = MONSTERFLAG_SPAWNED;
- if(!respwn)
- e.spawnflags |= MONSTERFLAG_NORESPAWN;
-
- if(invincible)
- e.spawnflags |= MONSTERFLAG_INVINCIBLE;
+ if(!respwn) { e.spawnflags |= MONSTERFLAG_NORESPAWN; }
+ if(invincible) { e.spawnflags |= MONSTERFLAG_INVINCIBLE; }
setorigin(e, orig);
{
RandomSelection_Init();
for(i = MON_FIRST; i <= MON_LAST; ++i)
- RandomSelection_Add(world, 0, (get_monsterinfo(i)).netname, 1, 1);
+ RandomSelection_Add(world, i, string_null, 1, 1);
- monster = RandomSelection_chosen_string;
+ monster_id = RandomSelection_chosen_float;
}
-
- if(monster != "")
+ else if(monster != "")
{
float found = 0;
entity mon;
}
}
if(!found)
- monster = (get_monsterinfo(MON_FIRST)).netname;
+ monster_id = ((monster_id > 0) ? monster_id : MON_FIRST);
}
- if(monster == "")
- if(monster_id)
- monster = (get_monsterinfo(monster_id)).netname;
-
e.realowner = spawnedby;
if(moveflag)
e.team = spawnedby.team; // colors handled in spawn code
if(autocvar_g_monsters_owners)
- e.monster_owner = own; // using .owner makes the monster non-solid for its master
+ e.monster_follow = own; // using .owner makes the monster non-solid for its master
- e.angles = spawnedby.angles;
+ e.angles_y = spawnedby.angles_y;
}
-
- //monster = strcat("$ spawnfunc_monster_", monster);
-
+
+ // Monster_Spawn checks if monster is valid
entity oldself = self;
self = e;
- monster_initialize(monster_id);
+ Monster_Spawn(monster_id);
self = oldself;
- //target_spawn_edit_entity(e, monster, world, world, world, world, world);
-
return e;
}
#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/campaign.qh"
#include "../../server/command/common.qh"
#include "../../server/tturrets/include/turrets.qh"
#endif
-// =========================
-// SVQC Monster Properties
-// =========================
-
+void monsters_setstatus()
+{
+ self.stat_monsters_total = monsters_total;
+ self.stat_monsters_killed = monsters_killed;
+}
void monster_dropitem()
{
}
}
-float Monster_SkillModifier()
-{
- float t = 0.5+self.monster_skill*((1.2-0.3)/10);
-
- return t;
-}
-
-float monster_isvalidtarget (entity targ, entity ent)
+void monster_makevectors(entity e)
{
- 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)
- //return false;
-
- if(targ.vehicle_flags & VHF_ISVEHICLE)
- if(!((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
-
- if(targ.takedamage == DAMAGE_NO)
- return false; // enemy can't be damaged
-
- if(targ.items & IT_INVISIBILITY)
- return false; // enemy is invisible
-
- if(substring(targ.classname, 0, 10) == "onslaught_")
- return false; // don't attack onslaught targets
-
- if(IS_SPEC(targ) || IS_OBSERVER(targ))
- return false; // enemy is a spectator
-
- if(!(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(self.flags & FL_MONSTER)
+ {
+ vector v;
- if(ent.monster_owner == targ)
- return false; // don't attack our master
+ v = e.origin + (e.mins + e.maxs) * 0.5;
+ self.v_angle = vectoangles(v - (self.origin + self.view_ofs));
+ self.v_angle_x = -self.v_angle_x;
+ }
- if(targ.monster_owner == ent)
- return false; // don't attack our pet
+ makevectors(self.v_angle);
+}
- if(!(targ.vehicle_flags & VHF_ISVEHICLE))
- if(targ.flags & FL_NOTARGET)
- return false; // enemy can't be targeted
+// ===============
+// Target handling
+// ===============
- if(!autocvar_g_monsters_typefrag)
- if(targ.BUTTON_CHAT)
- return false; // no typefragging!
+bool Monster_ValidTarget(entity mon, entity player)
+{
+ // 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
+ || (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))
+ || (mon.monster_follow == player || player.monster_follow == mon)
+ || (!(player.vehicle_flags & VHF_ISVEHICLE) && (player.flags & FL_NOTARGET))
+ || (!autocvar_g_monsters_typefrag && player.BUTTON_CHAT)
+ || (SAME_TEAM(player, mon))
+ || (player.frozen)
+ || (player.alpha != 0 && player.alpha < 0.5)
+ )
+ {
+ // if any of the above checks fail, target is not valid
+ return false;
+ }
- if(SAME_TEAM(targ, ent))
- return false; // enemy is on our team
+ traceline(mon.origin + self.view_ofs, player.origin, 0, mon);
- if (targ.frozen)
- return false; // ignore frozen
+ if((trace_fraction < 1) && (trace_ent != player))
+ return false;
- if(autocvar_g_monsters_target_infront || (ent.spawnflags & MONSTERFLAG_INFRONT))
- if(ent.enemy != targ)
+ if(autocvar_g_monsters_target_infront || (mon.spawnflags & MONSTERFLAG_INFRONT))
+ if(mon.enemy != player)
{
float dot;
- makevectors (ent.angles);
- dot = normalize (targ.origin - ent.origin) * v_forward;
+ makevectors (mon.angles);
+ dot = normalize (player.origin - mon.origin) * v_forward;
- if(dot <= 0.3)
- return false;
+ if(dot <= 0.3) { return false; }
}
- return true;
+ return true; // this target is valid!
}
-entity FindTarget (entity ent)
+entity Monster_FindTarget(entity mon)
{
- if(MUTATOR_CALLHOOK(MonsterFindTarget)) { return ent.enemy; } // Handled by a mutator
+ if(MUTATOR_CALLHOOK(MonsterFindTarget)) { return mon.enemy; } // Handled by a mutator
entity head, closest_target = world;
- head = findradius(ent.origin, ent.target_range);
- //head = WarpZone_FindRadius(ent.origin, ent.target_range, true);
+ head = findradius(mon.origin, mon.target_range);
while(head) // find the closest acceptable target to pass to
{
if(head.monster_attack)
- if(monster_isvalidtarget(head, ent))
+ if(Monster_ValidTarget(mon, head))
{
// 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 head_center = WarpZone_UnTransformOrigin(head, CENTER_OR_VIEWOFS(head));
- vector ent_center = CENTER_OR_VIEWOFS(ent);
-
- traceline(ent_center, head_center, MOVE_NORMAL, ent);
+ vector ent_center = CENTER_OR_VIEWOFS(mon);
- if(trace_ent == head)
if(closest_target)
{
vector closest_target_center = CENTER_OR_VIEWOFS(closest_target);
- //vector closest_target_center = WarpZone_UnTransformOrigin(closest_target, CENTER_OR_VIEWOFS(closest_target));
if(vlen(ent_center - head_center) < vlen(ent_center - closest_target_center))
{ closest_target = head; }
}
return closest_target;
}
-void MonsterTouch ()
+void monster_setupcolors(entity mon)
{
- if(other == world)
- return;
+ if(IS_PLAYER(mon.realowner))
+ mon.colormap = mon.realowner.colormap;
+ else if(teamplay && mon.team)
+ mon.colormap = 1024 + (mon.team - 1) * 17;
+ else
+ {
+ if(mon.monster_skill <= MONSTER_SKILL_EASY)
+ mon.colormap = 1029;
+ else if(mon.monster_skill <= MONSTER_SKILL_MEDIUM)
+ mon.colormap = 1027;
+ else if(mon.monster_skill <= MONSTER_SKILL_HARD)
+ mon.colormap = 1038;
+ else if(mon.monster_skill <= MONSTER_SKILL_INSANE)
+ mon.colormap = 1028;
+ else if(mon.monster_skill <= MONSTER_SKILL_NIGHTMARE)
+ mon.colormap = 1032;
+ else
+ mon.colormap = 1024;
+ }
+}
- if(self.enemy != other)
- if(!(other.flags & FL_MONSTER))
- if(monster_isvalidtarget(other, self))
- self.enemy = other;
+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'));
+
+ ent.sprite.team = newteam;
+ ent.sprite.SendFlags |= 1;
+ }
+}
+
+void Monster_Delay_Action()
+{
+ entity oldself = self;
+ self = self.owner;
+ if(Monster_ValidTarget(self, self.enemy)) { oldself.use(); }
+
+ if(oldself.cnt > 0)
+ {
+ oldself.cnt -= 1;
+ oldself.think = Monster_Delay_Action;
+ oldself.nextthink = time + oldself.respawn_time;
+ }
+ else
+ {
+ oldself.think = SUB_Remove;
+ oldself.nextthink = time;
+ }
+}
+
+void Monster_Delay(float repeat_count, float repeat_defer, float defer_amnt, void() func)
+{
+ // deferred attacking, checks if monster is still alive and target is still valid before attacking
+ entity e = spawn();
+
+ e.think = Monster_Delay_Action;
+ e.nextthink = time + defer_amnt;
+ e.count = defer_amnt;
+ e.owner = self;
+ e.use = func;
+ e.cnt = repeat_count;
+ e.respawn_time = repeat_defer;
}
+
+// ==============
+// Monster sounds
+// ==============
+
string get_monster_model_datafilename(string m, float sk, string fil)
{
if(m)
return strcat(m, ".", fil);
}
-void PrecacheMonsterSounds(string f)
+void Monster_Sound_Precache(string f)
{
float fh;
string s;
fclose(fh);
}
-void precache_monstersounds()
+void Monster_Sounds_Precache()
{
string m = (get_monsterinfo(self.monsterid)).model;
float globhandle, n, i;
{
//print(search_getfilename(globhandle, i), "\n");
f = search_getfilename(globhandle, i);
- PrecacheMonsterSounds(f);
+ Monster_Sound_Precache(f);
}
search_end(globhandle);
}
-void ClearMonsterSounds()
+void Monster_Sounds_Clear()
{
#define _MSOUND(m) if(self.monstersound_##m) { strunzone(self.monstersound_##m); self.monstersound_##m = string_null; }
ALLMONSTERSOUNDS
#undef _MSOUND
}
-.string GetMonsterSoundSampleField(string type)
+.string Monster_Sound_SampleField(string type)
{
GetMonsterSoundSampleField_notFound = 0;
switch(type)
return string_null;
}
-float LoadMonsterSounds(string f, float first)
+float Monster_Sounds_Load(string f, float first)
{
float fh;
string s;
if(fh < 0)
{
dprint("Monster sound file not found: ", f, "\n");
- return 0;
+ return false;
}
while((s = fgets(fh)))
{
if(tokenize_console(s) != 3)
continue;
- field = GetMonsterSoundSampleField(argv(0));
+ field = Monster_Sound_SampleField(argv(0));
if(GetMonsterSoundSampleField_notFound)
continue;
if (self.(field))
self.(field) = strzone(strcat(argv(1), " ", argv(2)));
}
fclose(fh);
- return 1;
+ return true;
}
.int skin_for_monstersound;
-void UpdateMonsterSounds()
+void Monster_Sounds_Update()
{
- entity mon = get_monsterinfo(self.monsterid);
+ if(self.skin == self.skin_for_monstersound) { return; }
- if(self.skin == self.skin_for_monstersound)
- return;
self.skin_for_monstersound = self.skin;
- ClearMonsterSounds();
- //LoadMonsterSounds("sound/monsters/default.sounds", 1);
- if(!autocvar_g_debug_defaultsounds)
- if(!LoadMonsterSounds(get_monster_model_datafilename(mon.model, self.skin, "sounds"), 0))
- LoadMonsterSounds(get_monster_model_datafilename(mon.model, 0, "sounds"), 0);
+ Monster_Sounds_Clear();
+ if(!Monster_Sounds_Load(get_monster_model_datafilename(self.model, self.skin, "sounds"), 0))
+ Monster_Sounds_Load(get_monster_model_datafilename(self.model, 0, "sounds"), 0);
}
-void MonsterSound(.string samplefield, float sound_delay, float delaytoo, float chan)
+void Monster_Sound(.string samplefield, float sound_delay, float delaytoo, float chan)
{
if(!autocvar_g_monsters_sounds) { return; }
self.msound_delay = time + sound_delay;
}
-void monster_makevectors(entity e)
+
+// =======================
+// Monster attack handlers
+// =======================
+
+float Monster_Attack_Melee(entity targ, float damg, vector anim, float er, float animtime, int deathtype, float dostop)
{
- vector v;
+ if(dostop && (self.flags & FL_MONSTER)) { self.state = MONSTER_ATTACK_MELEE; }
- v = e.origin + (e.mins + e.maxs) * 0.5;
- self.v_angle = vectoangles(v - (self.origin + self.view_ofs));
- self.v_angle_x = -self.v_angle.x;
+ setanim(self, anim, false, true, false);
- makevectors(self.v_angle);
+ if(self.animstate_endtime > time && (self.flags & FL_MONSTER))
+ self.attack_finished_single = self.anim_finished = self.animstate_endtime;
+ else
+ self.attack_finished_single = self.anim_finished = time + animtime;
+
+ monster_makevectors(targ);
+
+ traceline(self.origin + self.view_ofs, self.origin + v_forward * er, 0, self);
+
+ if(trace_ent.takedamage)
+ Damage(trace_ent, self, self, damg * MONSTER_SKILLMOD(self), deathtype, trace_ent.origin, normalize(trace_ent.origin - self.origin));
+
+ return true;
}
-float monster_melee(entity targ, float damg, float anim, float er, float anim_finished, int deathtype, float dostop)
+float Monster_Attack_Leap_Check(vector vel)
{
- if (self.health <= 0)
- return false; // attacking while dead?!
+ if(self.state && (self.flags & FL_MONSTER))
+ return false; // already attacking
+ if(!(self.flags & FL_ONGROUND))
+ return false; // not on the ground
+ if(self.health <= 0)
+ return false; // called when dead?
+ if(time < self.attack_finished_single)
+ return false; // still attacking
- if(dostop)
- {
- self.velocity_x = 0;
- self.velocity_y = 0;
- self.state = MONSTER_STATE_ATTACK_MELEE;
- }
+ vector old = self.velocity;
- self.frame = anim;
+ self.velocity = vel;
+ tracetoss(self, self);
+ self.velocity = old;
+ if (trace_ent != self.enemy)
+ return false;
- if(anim_finished != 0)
- self.attack_finished_single = time + anim_finished;
+ return true;
+}
- monster_makevectors(targ);
+bool Monster_Attack_Leap(vector anm, void() touchfunc, vector vel, float animtime)
+{
+ if(!Monster_Attack_Leap_Check(vel))
+ return false;
- traceline(self.origin + self.view_ofs, self.origin + v_forward * er, 0, self);
+ setanim(self, anm, false, true, false);
- if(trace_ent.takedamage)
- Damage(trace_ent, self, self, damg * Monster_SkillModifier(), deathtype, trace_ent.origin, normalize(trace_ent.origin - self.origin));
+ if(self.animstate_endtime > time && (self.flags & FL_MONSTER))
+ self.attack_finished_single = self.anim_finished = self.animstate_endtime;
+ else
+ self.attack_finished_single = self.anim_finished = time + animtime;
+
+ if(self.flags & FL_MONSTER)
+ self.state = MONSTER_ATTACK_RANGED;
+ self.touch = touchfunc;
+ self.origin_z += 1;
+ self.velocity = vel;
+ self.flags &= ~FL_ONGROUND;
return true;
}
-void Monster_CheckMinibossFlag ()
+void Monster_Attack_Check(entity e, entity targ)
+{
+ if((e == world || targ == world)
+ || (!e.monster_attackfunc)
+ || (time < e.attack_finished_single)
+ ) { return; }
+
+ float targ_vlen = vlen(targ.origin - e.origin);
+
+ if(targ_vlen <= e.attack_range)
+ {
+ float attack_success = e.monster_attackfunc(MONSTER_ATTACK_MELEE);
+ if(attack_success == 1)
+ Monster_Sound(monstersound_melee, 0, false, CH_VOICE);
+ else if(attack_success > 0)
+ return;
+ }
+
+ if(targ_vlen > e.attack_range)
+ {
+ float attack_success = e.monster_attackfunc(MONSTER_ATTACK_RANGED);
+ if(attack_success == 1)
+ Monster_Sound(monstersound_melee, 0, false, CH_VOICE);
+ else if(attack_success > 0)
+ return;
+ }
+}
+
+
+// ======================
+// Main monster functions
+// ======================
+
+void Monster_Touch()
+{
+ if(other == world) { return; }
+
+ if(other.monster_attack)
+ if(self.enemy != other)
+ if(!(self.flags & FL_MONSTER))
+ if(Monster_ValidTarget(self, other))
+ self.enemy = other;
+}
+
+void Monster_Miniboss_Check()
{
if(MUTATOR_CALLHOOK(MonsterCheckBossFlag))
return;
}
}
-float Monster_CanRespawn(entity ent)
+float Monster_Respawn_Check()
{
- other = ent;
- if(ent.deadflag == DEAD_DEAD) // don't call when monster isn't dead
- if(MUTATOR_CALLHOOK(MonsterRespawn))
- return true; // enabled by a mutator
-
- if(ent.spawnflags & MONSTERFLAG_NORESPAWN)
- return false;
-
- if(!autocvar_g_monsters_respawn)
- return false;
+ if(self.spawnflags & MONSTERFLAG_NORESPAWN) { return false; }
+ if(!autocvar_g_monsters_respawn) { return false; }
return true;
}
-void monster_respawn()
-{
- // is this function really needed?
- monster_initialize(self.monsterid);
-}
+void Monster_Respawn() { Monster_Spawn(self.monsterid); }
-void Monster_Fade ()
+void Monster_Dead_Fade()
{
- if(Monster_CanRespawn(self))
+ if(Monster_Respawn_Check())
{
self.spawnflags |= MONSTERFLAG_RESPAWNED;
- self.think = monster_respawn;
+ self.think = Monster_Respawn;
self.nextthink = time + self.respawntime;
self.monster_lifetime = 0;
self.deadflag = DEAD_RESPAWNING;
}
}
-float Monster_CanJump (vector vel)
-{
- if(self.state)
- return false; // already attacking
- if(!(self.flags & FL_ONGROUND))
- return false; // not on the ground
- if(self.health <= 0)
- return false; // called when dead?
- if(time < self.attack_finished_single)
- return false; // still attacking
-
- vector old = self.velocity;
-
- self.velocity = vel;
- tracetoss(self, self);
- self.velocity = old;
- if (trace_ent != self.enemy)
- return false;
-
- return true;
-}
-
-float monster_leap (float anm, void() touchfunc, vector vel, float anim_finished)
-{
- if(!Monster_CanJump(vel))
- return false;
-
- self.frame = anm;
- self.state = MONSTER_STATE_ATTACK_LEAP;
- self.touch = touchfunc;
- self.origin_z += 1;
- self.velocity = vel;
- self.flags &= ~FL_ONGROUND;
-
- self.attack_finished_single = time + anim_finished;
-
- return true;
-}
-
-void monster_checkattack(entity e, entity targ)
-{
- if(e == world)
- return;
- if(targ == world)
- return;
-
- if(!e.monster_attackfunc)
- return;
-
- if(time < e.attack_finished_single)
- return;
-
- if(vlen(targ.origin - e.origin) <= e.attack_range)
- if(e.monster_attackfunc(MONSTER_ATTACK_MELEE))
- {
- MonsterSound(monstersound_melee, 0, false, CH_VOICE);
- return;
- }
-
- if(vlen(targ.origin - e.origin) > e.attack_range)
- if(e.monster_attackfunc(MONSTER_ATTACK_RANGED))
- {
- MonsterSound(monstersound_ranged, 0, false, CH_VOICE);
- return;
- }
-}
-
-void monster_use ()
+void Monster_Use()
{
- if(!self.enemy)
- if(self.health > 0)
- if(monster_isvalidtarget(activator, self))
- self.enemy = activator;
+ if(Monster_ValidTarget(self, activator)) { self.enemy = activator; }
}
-.float last_trace;
-.float last_enemycheck; // for checking enemy
-vector monster_pickmovetarget(entity targ)
+vector Monster_Move_Target(entity targ)
{
// enemy is always preferred target
if(self.enemy)
|| (self.enemy.deadflag != DEAD_NO || self.enemy.health < 1)
|| (self.enemy.frozen)
|| (self.enemy.flags & FL_NOTARGET)
- || (self.enemy.alpha < 0.5)
+ || (self.enemy.alpha < 0.5 && self.enemy.alpha != 0)
|| (self.enemy.takedamage == DAMAGE_NO)
|| (vlen(self.origin - targ_origin) > self.target_range)
|| ((trace_fraction < 1) && (trace_ent != self.enemy)))
self.monster_movestate = MONSTER_MOVE_ENEMY;
self.last_trace = time + 1.2;
- return targ_origin;
+ if(self.monster_moveto)
+ return self.monster_moveto; // assumes code is properly setting this when monster has an enemy
+ else
+ return targ_origin;
}
/*makevectors(self.angles);
switch(self.monster_moveflags)
{
- case MONSTER_MOVE_OWNER:
+ case MONSTER_MOVE_FOLLOW:
{
- self.monster_movestate = MONSTER_MOVE_OWNER;
+ self.monster_movestate = MONSTER_MOVE_FOLLOW;
self.last_trace = time + 0.3;
- return (self.monster_owner) ? self.monster_owner.origin : self.origin;
+ return (self.monster_follow) ? self.monster_follow.origin : self.origin;
}
case MONSTER_MOVE_SPAWNLOC:
{
}
case MONSTER_MOVE_NOMOVE:
{
- self.monster_movestate = MONSTER_MOVE_NOMOVE;
- self.last_trace = time + 2;
+ if(self.monster_moveto)
+ {
+ self.last_trace = time + 0.5;
+ return self.monster_moveto;
+ }
+ else
+ {
+ self.monster_movestate = MONSTER_MOVE_NOMOVE;
+ self.last_trace = time + 2;
+ }
return self.origin;
}
default:
vector pos;
self.monster_movestate = MONSTER_MOVE_WANDER;
- if(targ)
+ if(self.monster_moveto)
+ {
+ self.last_trace = time + 0.5;
+ pos = self.monster_moveto;
+ }
+ else if(targ)
{
self.last_trace = time + 0.5;
pos = targ.origin;
}
}
-void monster_CalculateVelocity(entity mon, vector to, vector from, float turnrate, float movespeed)
+void Monster_CalculateVelocity(entity mon, vector to, vector from, float turnrate, float movespeed)
{
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, (current_distance / self.pass_distance)));
+ float current_height = (initial_height * min(1, (self.pass_distance) ? (current_distance / self.pass_distance) : current_distance));
//print("current_height = ", ftos(current_height), ", initial_height = ", ftos(initial_height), ".\n");
vector targpos;
//mon.angles = vectoangles(mon.velocity);
}
-void monster_move(float runspeed, float walkspeed, float stopspeed, float manim_run, float manim_walk, float manim_idle)
+void Monster_Move(float runspeed, float walkspeed, float stpspeed)
{
- //fixedmakevectors(self.angles);
-
- if(self.target2)
- self.goalentity = find(world, targetname, self.target2);
+ if(self.target2) { self.goalentity = find(world, targetname, self.target2); }
entity targ;
self.health = max(1, self.revive_progress * self.max_health);
self.iceblock.alpha = bound(0.2, 1 - self.revive_progress, 1);
- WaypointSprite_UpdateHealth(self.sprite, self.health);
+ if(!(self.spawnflags & MONSTERFLAG_INVINCIBLE) && self.sprite)
+ WaypointSprite_UpdateHealth(self.sprite, self.health);
- movelib_beak_simple(stopspeed);
- self.frame = manim_idle;
+ movelib_beak_simple(stpspeed);
+ setanim(self, self.anim_idle, true, false, false);
self.enemy = world;
self.nextthink = time + self.ticrate;
self.revive_progress = bound(0, self.revive_progress - self.ticrate * self.revive_speed, 1);
self.health = max(0, autocvar_g_nades_ice_health + (self.max_health-autocvar_g_nades_ice_health) * self.revive_progress );
- WaypointSprite_UpdateHealth(self.sprite, self.health);
+ if(!(self.spawnflags & MONSTERFLAG_INVINCIBLE) && self.sprite)
+ WaypointSprite_UpdateHealth(self.sprite, self.health);
- movelib_beak_simple(stopspeed);
- self.frame = manim_idle;
+ movelib_beak_simple(stpspeed);
+ setanim(self, self.anim_idle, true, false, false);
self.enemy = world;
self.nextthink = time + self.ticrate;
{
Unfreeze(self);
self.health = 0;
- self.event_damage(self, self.frozen_by, 1, DEATH_NADE_ICE_FREEZE, self.origin, '0 0 0');
+ if(self.event_damage)
+ self.event_damage(self, self.frozen_by, 1, DEATH_NADE_ICE_FREEZE, self.origin, '0 0 0');
}
else if ( self.revive_progress <= 0 )
{
if(time >= self.last_trace)
{
- self.fish_wasdrowning = true;
self.last_trace = time + 0.4;
Damage (self, world, world, 2, DEATH_DROWN, self.origin, '0 0 0');
return;
}
- else if(self.fish_wasdrowning)
+ else if(self.movetype == MOVETYPE_BOUNCE)
{
- self.fish_wasdrowning = false;
self.angles_x = 0;
self.movetype = MOVETYPE_WALK;
}
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))
+ || (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.frame = manim_idle;
- movelib_beak_simple(stopspeed);
+ setanim(self, self.anim_idle, true, false, false);
+ movelib_beak_simple(stpspeed);
return;
}
targ = monster_target;
- runspeed = bound(0, monster_speed_run * Monster_SkillModifier(), runspeed * 2); // limit maxspeed to prevent craziness
- walkspeed = bound(0, monster_speed_walk * Monster_SkillModifier(), walkspeed * 2); // limit maxspeed to prevent craziness
+ runspeed = bound(0, monster_speed_run * MONSTER_SKILLMOD(self), runspeed * 2.5); // limit maxspeed to prevent craziness
+ walkspeed = bound(0, monster_speed_walk * MONSTER_SKILLMOD(self), walkspeed * 2.5); // limit maxspeed to prevent craziness
if(time < self.spider_slowness)
{
if(teamplay)
if(autocvar_g_monsters_teams)
- if(DIFF_TEAM(self.monster_owner, self))
- self.monster_owner = world;
+ if(DIFF_TEAM(self.monster_follow, self))
+ self.monster_follow = world;
if(time >= self.last_enemycheck)
{
if(!self.enemy)
{
- self.enemy = FindTarget(self);
+ self.enemy = Monster_FindTarget(self);
if(self.enemy)
{
WarpZone_RefSys_Copy(self.enemy, self);
WarpZone_RefSys_AddInverse(self.enemy, self); // wz1^-1 ... wzn^-1 receiver
self.moveto = WarpZone_RefSys_TransformOrigin(self.enemy, self, (0.5 * (self.enemy.absmin + self.enemy.absmax)));
-
- 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)));
- MonsterSound(monstersound_sight, 0, false, CH_VOICE);
+ 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);
}
}
self.last_enemycheck = time + 1; // check for enemies every second
}
- if(self.state == MONSTER_STATE_ATTACK_MELEE && time >= self.attack_finished_single)
+ if(self.state == MONSTER_ATTACK_RANGED && (self.flags & FL_ONGROUND))
+ {
self.state = 0;
+ self.touch = Monster_Touch;
+ }
- if(self.state != MONSTER_STATE_ATTACK_MELEE) // don't move if set
- if(time >= self.last_trace || self.enemy) // update enemy instantly
- self.moveto = monster_pickmovetarget(targ);
+ if(self.state && time >= self.attack_finished_single)
+ self.state = 0; // attack is over
- if(!self.enemy)
- MonsterSound(monstersound_idle, 7, true, CH_VOICE);
+ if(self.state != MONSTER_ATTACK_MELEE) // don't move if set
+ if(time >= self.last_trace || self.enemy) // update enemy or rider instantly
+ self.moveto = Monster_Move_Target(targ);
- if(self.state == MONSTER_STATE_ATTACK_LEAP && (self.flags & FL_ONGROUND))
- {
- self.state = 0;
- self.touch = MonsterTouch;
- }
+ if(!self.enemy)
+ Monster_Sound(monstersound_idle, 7, true, CH_VOICE);
- if(self.state == MONSTER_STATE_ATTACK_MELEE)
+ if(self.state == MONSTER_ATTACK_MELEE)
self.moveto = self.origin;
if(self.enemy && self.enemy.vehicle)
runspeed = 0;
- if(!(((self.flags & FL_FLY) && (self.spawnflags & MONSTERFLAG_FLY_VERTICAL)) || (self.flags & FL_SWIM)))
- //v_forward = normalize(self.moveto - self.origin);
- //else
- self.moveto_z = self.origin.z;
+ if(!(self.spawnflags & MONSTERFLAG_FLY_VERTICAL) && !(self.flags & FL_SWIM))
+ self.moveto_z = self.origin_z;
- if(vlen(self.origin - self.moveto) > 64)
+ if(vlen(self.origin - self.moveto) > 100)
{
+ float do_run = !!(self.enemy);
if((self.flags & FL_ONGROUND) || ((self.flags & FL_FLY) || (self.flags & FL_SWIM)))
- monster_CalculateVelocity(self, self.moveto, self.origin, true, ((self.enemy) ? runspeed : walkspeed));
-
- /*&if(self.flags & FL_FLY || self.flags & FL_SWIM)
- movelib_move_simple(v_forward, ((self.enemy) ? runspeed : walkspeed), 0.6);
- else
- movelib_move_simple_gravity(v_forward, ((self.enemy) ? runspeed : walkspeed), 0.6); */
+ Monster_CalculateVelocity(self, self.moveto, self.origin, true, ((do_run) ? runspeed : walkspeed));
- if(time > self.pain_finished)
- if(time > self.attack_finished_single)
+ if(time > self.pain_finished) // TODO: use anim_finished instead!
+ if(!self.state)
+ if(time > self.anim_finished)
if(vlen(self.velocity) > 10)
- self.frame = ((self.enemy) ? manim_run : manim_walk);
+ setanim(self, ((do_run) ? self.anim_run : self.anim_walk), true, false, false);
else
- self.frame = manim_idle;
+ setanim(self, self.anim_idle, true, false, false);
}
else
{
else if(e.target)
self.target2 = e.target;
- movelib_beak_simple(stopspeed);
- if(time > self.attack_finished_single)
+ movelib_beak_simple(stpspeed);
+ if(time > self.anim_finished)
if(time > self.pain_finished)
- if (vlen(self.velocity) <= 30)
- self.frame = manim_idle;
+ if(!self.state)
+ if(vlen(self.velocity) <= 30)
+ setanim(self, self.anim_idle, true, false, false);
}
- self.steerto = steerlib_attract2(self.moveto, 0.5, 500, 0.95);
+ self.steerto = steerlib_attract2(((self.monster_face) ? self.monster_face : self.moveto), 0.5, 500, 0.95);
vector real_angle = vectoangles(self.steerto) - self.angles;
float turny = 25;
- if(self.state == MONSTER_STATE_ATTACK_MELEE)
+ if(self.state == MONSTER_ATTACK_MELEE)
turny = 0;
if(turny)
{
self.angles_y += turny;
}
- monster_checkattack(self, self.enemy);
+ Monster_Attack_Check(self, self.enemy);
}
-void monster_remove(entity mon)
+void Monster_Remove(entity mon)
{
- if(!mon)
- return; // nothing to remove
-
- pointparticles(particleeffectnum("item_pickup"), mon.origin, '0 0 0', 1);
+ if(!mon) { return; }
- if(mon.weaponentity)
- remove(mon.weaponentity);
-
- if(mon.iceblock)
- remove(mon.iceblock);
+ if(!MUTATOR_CALLHOOK(MonsterRemove))
+ pointparticles(particleeffectnum("item_pickup"), mon.origin, '0 0 0', 1);
+ if(mon.weaponentity) { remove(mon.weaponentity); }
+ if(mon.iceblock) { remove(mon.iceblock); }
WaypointSprite_Kill(mon.sprite);
-
remove(mon);
}
-void monster_dead_think()
+void Monster_Dead_Think()
{
self.nextthink = time + self.ticrate;
- CSQCMODEL_AUTOUPDATE();
-
if(self.monster_lifetime != 0)
if(time >= self.monster_lifetime)
{
- Monster_Fade();
+ Monster_Dead_Fade();
return;
}
}
-void monsters_setstatus()
-{
- self.stat_monsters_total = monsters_total;
- self.stat_monsters_killed = monsters_killed;
-}
-
void Monster_Appear()
{
self.enemy = activator;
self.spawnflags &= ~MONSTERFLAG_APPEAR; // otherwise, we get an endless loop
- monster_initialize(self.monsterid);
+ Monster_Spawn(self.monsterid);
}
-float Monster_CheckAppearFlags(entity ent, float monster_id)
+float Monster_Appear_Check(entity ent, float monster_id)
{
if(!(ent.spawnflags & MONSTERFLAG_APPEAR))
return false;
return true;
}
-void monsters_reset()
+void Monster_Reset()
{
setorigin(self, self.pos1);
self.angles = self.pos2;
self.moveto = self.origin;
}
-void monsters_corpse_damage (entity inflictor, entity attacker, float damage, int deathtype, vector hitloc, vector force)
+void Monster_Dead_Damage(entity inflictor, entity attacker, float damage, int deathtype, vector hitloc, vector force)
{
self.health -= damage;
}
}
-void monster_die(entity attacker, float gibbed)
+void Monster_Dead(entity attacker, float gibbed)
{
- self.think = monster_dead_think;
+ self.think = Monster_Dead_Think;
self.nextthink = time;
self.monster_lifetime = time + 5;
monster_dropitem();
- MonsterSound(monstersound_death, 0, false, CH_VOICE);
+ Monster_Sound(monstersound_death, 0, false, CH_VOICE);
if(!(self.spawnflags & MONSTERFLAG_SPAWNED) && !(self.spawnflags & MONSTERFLAG_RESPAWNED))
monsters_killed += 1;
totalspawned -= 1;
}
- if(self.candrop && self.weapon)
- W_ThrowNewWeapon(self, self.weapon, 0, self.origin, randomvec() * 150 + '0 0 325');
-
- self.event_damage = ((gibbed) ? func_null : monsters_corpse_damage);
+ self.event_damage = ((gibbed) ? func_null : Monster_Dead_Damage);
self.solid = SOLID_CORPSE;
self.takedamage = DAMAGE_AIM;
self.deadflag = DEAD_DEAD;
self.enemy = world;
self.movetype = MOVETYPE_TOSS;
self.moveto = self.origin;
- self.touch = MonsterTouch; // reset incase monster was pouncing
+ self.touch = Monster_Touch; // reset incase monster was pouncing
self.reset = func_null;
self.state = 0;
self.attack_finished_single = 0;
+ self.effects = 0;
if(!((self.flags & FL_FLY) || (self.flags & FL_SWIM)))
self.velocity = '0 0 0';
+ CSQCModel_UnlinkEntity();
+
MON_ACTION(self.monsterid, MR_DEATH);
+
+ if(self.candrop && self.weapon)
+ W_ThrowNewWeapon(self, self.weapon, 0, self.origin, randomvec() * 150 + '0 0 325');
}
-void monsters_damage (entity inflictor, entity attacker, float damage, int deathtype, vector hitloc, vector force)
+void Monster_Damage(entity inflictor, entity attacker, float damage, int deathtype, vector hitloc, vector force)
{
- if(self.frozen && deathtype != DEATH_KILL && deathtype != DEATH_NADE_ICE_FREEZE)
- return;
-
if((self.spawnflags & MONSTERFLAG_INVINCIBLE) && deathtype != DEATH_KILL)
return;
- if(time < self.pain_finished && deathtype != DEATH_KILL)
+ if(self.frozen && deathtype != DEATH_KILL && deathtype != DEATH_NADE_ICE_FREEZE)
return;
+ //if(time < self.pain_finished && deathtype != DEATH_KILL)
+ //return;
+
if(time < self.spawnshieldtime && deathtype != DEATH_KILL)
return;
vector v;
float take, save;
- v = healtharmor_applydamage(self.armorvalue, self.m_armor_blockpercent, deathtype, damage);
- take = v.x;
- save = v.y;
+ v = healtharmor_applydamage(100, self.armorvalue / 100, deathtype, damage);
+ take = v_x;
+ save = v_y;
- self.health -= take;
+ damage_take = take;
+ frag_attacker = attacker;
+ frag_deathtype = deathtype;
+ MON_ACTION(self.monsterid, MR_PAIN);
+ take = damage_take;
+
+ if(take)
+ {
+ self.health -= take;
+ Monster_Sound(monstersound_pain, 1.2, true, CH_PAIN);
+ }
- WaypointSprite_UpdateHealth(self.sprite, self.health);
+ if(self.sprite)
+ WaypointSprite_UpdateHealth(self.sprite, self.health);
self.dmg_time = time;
self.velocity += force * self.damageforcescale;
- if(deathtype != DEATH_DROWN)
+ if(deathtype != DEATH_DROWN && take)
{
Violence_GibSplash_At(hitloc, force, 2, bound(0, take, 200) / 16, self, attacker);
if (take > 50)
SUB_UseTargets();
self.target2 = self.oldtarget2; // reset to original target on death, incase we respawn
- monster_die(attacker, (self.health <= -100 || deathtype == DEATH_KILL));
+ Monster_Dead(attacker, (self.health <= -100 || deathtype == DEATH_KILL));
WaypointSprite_Kill(self.sprite);
}
}
-void monster_setupcolors(entity mon)
+// don't check for enemies, just keep walking in a straight line
+void Monster_Move_2D(float mspeed, float allow_jumpoff)
{
- if(IS_PLAYER(mon.monster_owner))
- mon.colormap = mon.monster_owner.colormap;
- else if(teamplay && mon.team)
- mon.colormap = 1024 + (mon.team - 1) * 17;
- else
+ 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)
{
- if(mon.monster_skill <= MONSTER_SKILL_EASY)
- mon.colormap = 1029;
- else if(mon.monster_skill <= MONSTER_SKILL_MEDIUM)
- mon.colormap = 1027;
- else if(mon.monster_skill <= MONSTER_SKILL_HARD)
- mon.colormap = 1038;
- else if(mon.monster_skill <= MONSTER_SKILL_INSANE)
- mon.colormap = 1028;
- else if(mon.monster_skill <= MONSTER_SKILL_NIGHTMARE)
- mon.colormap = 1032;
- else
- mon.colormap = 1024;
+ mspeed = 0;
+ if(time >= self.spawn_time)
+ setanim(self, self.anim_idle, true, false, false);
+ movelib_beak_simple(0.6);
+ return;
}
-}
-
-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)
+ 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)
{
- WaypointSprite_UpdateTeamRadar(ent.sprite, RADARICON_DANGER, ((newteam) ? Team_ColorRGB(newteam) : '1 0 0'));
-
- ent.sprite.team = newteam;
- ent.sprite.SendFlags |= 1;
+ 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)
+ {
+ a = b - '0 0 32';
+ traceline(b, a, MOVE_WORLDONLY, self);
+ 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_walk, true, false, false);
+ else
+ setanim(self, self.anim_idle, true, false, false);
}
-void monster_think()
+void Monster_Think()
{
- self.think = monster_think;
+ self.think = Monster_Think;
self.nextthink = self.ticrate;
if(self.monster_lifetime)
return;
}
- MON_ACTION(self.monsterid, MR_THINK);
+ if(MON_ACTION(self.monsterid, MR_THINK))
+ Monster_Move(self.speed2, self.speed, self.stopspeed);
CSQCMODEL_AUTOUPDATE();
}
-float monster_spawn()
+float Monster_Spawn_Setup()
{
MON_ACTION(self.monsterid, MR_SETUP);
+ // ensure some basic needs are met
+ if(!self.health) { self.health = 100; }
+ if(!self.armorvalue) { self.armorvalue = bound(0.2, 0.5 * MONSTER_SKILLMOD(self), 0.9); }
+ if(!self.target_range) { self.target_range = autocvar_g_monsters_target_range; }
+ if(!self.respawntime) { self.respawntime = autocvar_g_monsters_respawn_delay; }
+ if(!self.monster_moveflags) { self.monster_moveflags = MONSTER_MOVE_WANDER; }
+ if(!self.attack_range) { self.attack_range = autocvar_g_monsters_attack_range; }
+ if(!self.damageforcescale) { self.damageforcescale = autocvar_g_monsters_damageforcescale; }
+
if(!(self.spawnflags & MONSTERFLAG_RESPAWNED))
{
- Monster_CheckMinibossFlag();
- self.health *= Monster_SkillModifier();
+ Monster_Miniboss_Check();
+ self.health *= MONSTER_SKILLMOD(self);
+
+ if(!self.skin)
+ self.skin = rint(random() * 4);
}
self.max_health = self.health;
self.pain_finished = self.nextthink;
- if(IS_PLAYER(self.monster_owner))
+ if(IS_PLAYER(self.monster_follow))
self.effects |= EF_DIMLIGHT;
- if(!(self.spawnflags & MONSTERFLAG_RESPAWNED))
- if(!self.skin)
- self.skin = rint(random() * 4);
-
- if(!self.attack_range)
- self.attack_range = autocvar_g_monsters_attack_range;
-
if(!self.wander_delay) { self.wander_delay = 2; }
if(!self.wander_distance) { self.wander_distance = 600; }
- precache_monstersounds();
- UpdateMonsterSounds();
+ Monster_Sounds_Precache();
+ Monster_Sounds_Update();
if(teamplay)
self.monster_attack = true; // we can have monster enemies in team games
- MonsterSound(monstersound_spawn, 0, false, CH_VOICE);
+ Monster_Sound(monstersound_spawn, 0, false, CH_VOICE);
- WaypointSprite_Spawn(M_NAME(self.monsterid), 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'));
- if(!(self.spawnflags & MONSTERFLAG_INVINCIBLE))
+ if(autocvar_g_monsters_healthbars)
{
- WaypointSprite_UpdateMaxHealth(self.sprite, self.max_health);
- WaypointSprite_UpdateHealth(self.sprite, self.health);
+ 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'));
+
+ if(!(self.spawnflags & MONSTERFLAG_INVINCIBLE))
+ {
+ WaypointSprite_UpdateMaxHealth(self.sprite, self.max_health);
+ WaypointSprite_UpdateHealth(self.sprite, self.health);
+ }
}
- self.think = monster_think;
+ self.think = Monster_Think;
self.nextthink = time + self.ticrate;
if(MUTATOR_CALLHOOK(MonsterSpawn))
return true;
}
-float monster_initialize(float mon_id)
+bool Monster_Spawn(int mon_id)
{
- if(!autocvar_g_monsters) { return false; }
- if(!(self.spawnflags & MONSTERFLAG_RESPAWNED)) { MON_ACTION(mon_id, MR_PRECACHE); }
- if(Monster_CheckAppearFlags(self, mon_id)) { return true; } // return true so the monster isn't removed
-
+ // setup the basic required properties for a monster
entity mon = get_monsterinfo(mon_id);
+ if(!mon.monsterid) { return false; } // invalid monster
+
+ if(!autocvar_g_monsters) { Monster_Remove(self); return false; }
+
+ self.mdl = mon.model;
+ if(Monster_Appear_Check(self, mon_id)) { return true; } // return true so the monster isn't removed
if(!self.monster_skill)
self.monster_skill = cvar("g_monsters_skill");
// support for quake style removing monsters based on skill
- if(self.monster_skill == MONSTER_SKILL_EASY) if(self.spawnflags & MONSTERSKILL_NOTEASY) { return false; }
- if(self.monster_skill == MONSTER_SKILL_MEDIUM) if(self.spawnflags & MONSTERSKILL_NOTMEDIUM) { return false; }
- if(self.monster_skill == MONSTER_SKILL_HARD) if(self.spawnflags & MONSTERSKILL_NOTHARD) { return false; }
+ if(self.monster_skill == MONSTER_SKILL_EASY) if(self.spawnflags & MONSTERSKILL_NOTEASY) { Monster_Remove(self); return false; }
+ if(self.monster_skill == MONSTER_SKILL_MEDIUM) if(self.spawnflags & MONSTERSKILL_NOTMEDIUM) { Monster_Remove(self); return false; }
+ if(self.monster_skill == MONSTER_SKILL_HARD) if(self.spawnflags & MONSTERSKILL_NOTHARD) { Monster_Remove(self); return false; }
if(self.team && !teamplay)
self.team = 0;
if(!(self.spawnflags & MONSTERFLAG_RESPAWNED)) // don't count re-spawning monsters either
monsters_total += 1;
- setmodel(self, mon.model);
- //setsize(self, mon.mins, mon.maxs);
+ setmodel(self, self.mdl);
self.flags = FL_MONSTER;
+ self.classname = "monster";
self.takedamage = DAMAGE_AIM;
self.bot_attack = true;
self.iscreature = true;
self.teleportable = true;
self.damagedbycontents = true;
self.monsterid = mon_id;
- self.damageforcescale = 0;
- self.event_damage = monsters_damage;
- self.touch = MonsterTouch;
- self.use = monster_use;
+ self.event_damage = Monster_Damage;
+ self.touch = Monster_Touch;
+ self.use = Monster_Use;
self.solid = SOLID_BBOX;
self.movetype = MOVETYPE_WALK;
self.spawnshieldtime = time + autocvar_g_monsters_spawnshieldtime;
self.moveto = self.origin;
self.pos1 = self.origin;
self.pos2 = self.angles;
- self.reset = monsters_reset;
+ self.reset = Monster_Reset;
self.netname = mon.netname;
- self.monster_name = M_NAME(mon_id);
+ self.monster_attackfunc = mon.monster_attackfunc;
+ self.monster_name = mon.monster_name;
self.candrop = true;
- self.view_ofs = '0 0 1' * (self.maxs.z * 0.5);
+ self.view_ofs = '0 0 0.7' * (self.maxs_z * 0.5);
self.oldtarget2 = self.target2;
self.pass_distance = 0;
self.deadflag = DEAD_NO;
self.spawn_time = time;
self.spider_slowness = 0;
self.gravity = 1;
+ 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; // owner may carry their monster
-
- if(autocvar_g_fullbrightplayers)
- self.effects |= EF_FULLBRIGHT;
-
- if(autocvar_g_nodepthtestplayers)
- self.effects |= EF_NODEPTHTEST;
-
- if(mon.spawnflags & MONSTER_TYPE_SWIM)
- self.flags |= FL_SWIM;
+
+ if(!self.scale) { self.scale = 1; }
+ if(autocvar_g_monsters_edit) { self.grab = 1; }
+ if(autocvar_g_fullbrightplayers) { self.effects |= EF_FULLBRIGHT; }
+ if(autocvar_g_nodepthtestplayers) { self.effects |= EF_NODEPTHTEST; }
+ if(mon.spawnflags & MONSTER_TYPE_SWIM) { self.flags |= FL_SWIM; }
if(mon.spawnflags & MONSTER_TYPE_FLY)
{
setsize(self, mon.mins * self.scale, mon.maxs * self.scale);
- if(!self.ticrate)
- self.ticrate = autocvar_g_monsters_think_delay;
+ self.ticrate = bound(sys_frametime, ((!self.ticrate) ? autocvar_g_monsters_think_delay : self.ticrate), 60);
- self.ticrate = bound(sys_frametime, self.ticrate, 60);
-
- if(!self.m_armor_blockpercent)
- self.m_armor_blockpercent = 0.5;
-
- if(!self.target_range)
- self.target_range = autocvar_g_monsters_target_range;
-
- if(!self.respawntime)
- self.respawntime = autocvar_g_monsters_respawn_delay;
-
- if(!self.monster_moveflags)
- self.monster_moveflags = MONSTER_MOVE_WANDER;
+ if(!Monster_Spawn_Setup())
+ {
+ Monster_Remove(self);
+ return false;
+ }
if(!self.noalign)
{
setorigin(self, trace_endpos);
}
- if(!monster_spawn())
- return false;
-
if(!(self.spawnflags & MONSTERFLAG_RESPAWNED))
monster_setupcolors(self);
#ifndef SV_MONSTERS_H
#define SV_MONSTERS_H
-.string spawnmob;
-.float monster_attack;
+// stats networking
+.int stat_monsters_killed;
+.int stat_monsters_total;
+int monsters_total;
+int monsters_killed;
+
+// monster properties
+.int monster_movestate; // move target priority
+.entity monster_follow; // follow target
+.float wander_delay; // logic delay between moving while idle
+.float wander_distance; // distance to move between wander delays
+.float monster_lifetime; // monster dies instantly after this delay, set from spawn
+.float attack_range; // melee attack if closer, ranged attack if further away (TODO: separate ranged attack range?)
+.float spawn_time; // delay monster thinking until spawn animation has completed
+.bool candrop; // toggle to allow disabling monster item drops
+.int monster_movestate; // will be phased out
+.int monster_moveflags;
+.string oldtarget2; // a copy of the original follow target string
+.float last_trace; // logic delay between target tracing
+.float last_enemycheck; // for checking enemy
+.float anim_finished; // will be phased out when we have proper animations system
+.vector monster_moveto; // custom destination for monster (reset to '0 0 0' when you're done!)
+.vector monster_face; // custom looking direction for monster (reset to '0 0 0' when you're done!)
+.float speed2; // run speed
+.float stopspeed;
+.int oldskin;
+.string mdl_dead; // dead model for goombas
+
+#define MONSTER_SKILLMOD(mon) (0.5 + mon.monster_skill * ((1.2 - 0.3) / 10))
+
+// other properties
+.bool monster_attack; // indicates whether an entity can be attacked by monsters
+.float spider_slowness; // effect time of slowness inflicted by spiders
+
+// monster state declarations
+const int MONSTER_MOVE_FOLLOW = 1; // monster will follow if in range, or stand still
+const int MONSTER_MOVE_WANDER = 2; // monster will ignore owner & wander around
+const int MONSTER_MOVE_SPAWNLOC = 3; // monster will move to its spawn location when not attacking
+const int MONSTER_MOVE_NOMOVE = 4; // monster simply stands still
+const int MONSTER_MOVE_ENEMY = 5; // used only as a movestate
+const int MONSTER_ATTACK_MELEE = 6;
+const int MONSTER_ATTACK_RANGED = 7;
-.entity monster_owner; // new monster owner entity, fixes non-solid monsters
+// skill declarations
+const int MONSTER_SKILL_EASY = 1;
+const int MONSTER_SKILL_MEDIUM = 3;
+const int MONSTER_SKILL_HARD = 5;
+const int MONSTER_SKILL_INSANE = 7;
+const int MONSTER_SKILL_NIGHTMARE = 10;
-.float stat_monsters_killed; // stats
-.float stat_monsters_total;
-float monsters_total;
-float monsters_killed;
-void monsters_setstatus(); // monsters.qc
-.float monster_moveflags; // checks where to move when not attacking
+const int MONSTERSKILL_NOTEASY = 256; // monster will not spawn on skill <= 1
+const int MONSTERSKILL_NOTMEDIUM = 512; // monster will not spawn on skill 2
+const int MONSTERSKILL_NOTHARD = 1024; // monster will not spawn on skill >= 3
-.float wander_delay;
-.float wander_distance;
+// spawn flags
+const int MONSTERFLAG_APPEAR = 2; // delay spawn until triggered
+const int MONSTERFLAG_NORESPAWN = 4;
+const int MONSTERFLAG_FLY_VERTICAL = 8; // fly/swim vertically
+const int MONSTERFLAG_INFRONT = 32; // only check for enemies infront of us
+const int MONSTERFLAG_MINIBOSS = 64; // monster spawns as mini-boss (also has a chance of naturally becoming one)
+const int MONSTERFLAG_INVINCIBLE = 128; // monster doesn't take damage (may be used for map objects & temporary monsters)
+const int MONSTERFLAG_SPAWNED = 16384; // flag for spawned monsters
+const int MONSTERFLAG_RESPAWNED = 32768; // flag for re-spawned monsters
+// compatibility with old maps (soon to be removed)
.float monster_lifetime;
+.int monster_skill;
-.float spider_slowness; // special spider timer
+// functions used elsewhere
+void Monster_Remove(entity mon);
-void monster_remove(entity mon); // removes a monster
+void monsters_setstatus();
-.float(float attack_type) monster_attackfunc;
-const int MONSTER_ATTACK_MELEE = 1;
-const int MONSTER_ATTACK_RANGED = 2;
+bool Monster_Spawn(int mon_id);
-.float monster_skill;
-const float MONSTER_SKILL_EASY = 1;
-const float MONSTER_SKILL_MEDIUM = 3;
-const float MONSTER_SKILL_HARD = 5;
-const float MONSTER_SKILL_INSANE = 7;
-const float MONSTER_SKILL_NIGHTMARE = 10;
+void monster_setupcolors(entity mon);
+
+void Monster_Touch();
-.float fish_wasdrowning; // used to reset a drowning fish's angles if it reaches water again
+void Monster_Move_2D(float mspeed, float allow_jumpoff);
-.float candrop;
+void Monster_Delay(float repeat_count, float repeat_defer, float defer_amnt, void() func);
-.float attack_range;
+float Monster_Attack_Melee(entity targ, float damg, vector anim, float er, float animtime, int deathtype, float dostop);
-.float spawn_time; // stop monster from moving around right after spawning
+bool Monster_Attack_Leap(vector anm, void() touchfunc, vector vel, float animtime);
-.string oldtarget2;
-.float lastshielded;
+entity Monster_FindTarget(entity mon);
-.vector oldangles;
+void monster_makevectors(entity e);
-.float m_armor_blockpercent;
+void Monster_Sound(.string samplefield, float sound_delay, float delaytoo, float chan);
// monster sounds
-// copied from player sounds
.float msound_delay; // temporary antilag system
#define ALLMONSTERSOUNDS \
_MSOUND(death) \
_MSOUND(melee) \
_MSOUND(pain) \
_MSOUND(spawn) \
- _MSOUND(idle)
+ _MSOUND(idle) \
+ _MSOUND(attack)
#define _MSOUND(m) .string monstersound_##m;
ALLMONSTERSOUNDS
float GetMonsterSoundSampleField_notFound;
-const int MONSTERSKILL_NOTEASY = 256; // monster will not spawn on skill <= 1
-const int MONSTERSKILL_NOTMEDIUM = 512; // monster will not spawn on skill 2
-const int MONSTERSKILL_NOTHARD = 1024; // monster will not spawn on skill >= 3
-
-// new flags
-const int MONSTERFLAG_APPEAR = 2; // delay spawn until triggered
-const int MONSTERFLAG_NORESPAWN = 4;
-const int MONSTERFLAG_FLY_VERTICAL = 8; // fly/swim vertically
-const int MONSTERFLAG_INFRONT = 32; // only check for enemies infront of us
-const int MONSTERFLAG_MINIBOSS = 64; // monster spawns as mini-boss (also has a chance of naturally becoming one)
-const int MONSTERFLAG_INVINCIBLE = 128; // monster doesn't take damage (may be used for map objects & temporary monsters)
-const int MONSTERFLAG_SPAWNED = 16384; // flag for spawned monsters
-const int MONSTERFLAG_RESPAWNED = 32768; // flag for re-spawned monsters
-
-.int monster_movestate; // used to tell what the monster is currently doing
-const int MONSTER_MOVE_OWNER = 1; // monster will move to owner if in range, or stand still
-const int MONSTER_MOVE_WANDER = 2; // monster will ignore owner & wander around
-const int MONSTER_MOVE_SPAWNLOC = 3; // monster will move to its spawn location when not attacking
-const int MONSTER_MOVE_NOMOVE = 4; // monster simply stands still
-const int MONSTER_MOVE_ENEMY = 5; // used only as a movestate
-
-const int MONSTER_STATE_ATTACK_LEAP = 1;
-const int MONSTER_STATE_ATTACK_MELEE = 2;
-
-float monster_initialize(float mon_id);
-float monster_leap (float anm, void() touchfunc, vector vel, float anim_finished);
-void monster_makevectors(entity e);
-float monster_melee(entity targ, float damg, float anim, float er, float anim_finished, int deathtype, float dostop);
-void monster_move(float runspeed, float walkspeed, float stopspeed, float manim_run, float manim_walk, float manim_idle);
-void monster_setupcolors(entity mon);
-float Monster_SkillModifier();
-void MonsterTouch ();
#endif
float autocvar_g_monsters_think_delay;
int autocvar_g_monsters_max;
int autocvar_g_monsters_max_perplayer;
+float autocvar_g_monsters_damageforcescale = 0.8;
float autocvar_g_monsters_target_range;
bool autocvar_g_monsters_target_infront;
float autocvar_g_monsters_attack_range;
float autocvar_g_monsters_respawn_delay;
bool autocvar_g_monsters_respawn;
float autocvar_g_monsters_armor_blockpercent;
+float autocvar_g_monsters_healthbars;
+float autocvar_g_monsters_lineofsight;
float autocvar_g_touchexplode_radius;
float autocvar_g_touchexplode_damage;
float autocvar_g_touchexplode_edgedamage;
#include "../../common/teams.qh"
#include "../../common/util.qh"
-#include "../../common/monsters/all.qh"
-#include "../../common/monsters/spawn.qh"
-#include "../../common/monsters/sv_monsters.qh"
-
#include "../../warpzonelib/common.qh"
void ClientKill_TeamChange (float targetteam); // 0 = don't change, -1 = auto, -2 = spec
}
}
-void ClientCommand_mobedit(float request, float argc)
-{
- switch(request)
- {
- case CMD_REQUEST_COMMAND:
- {
- if(argv(1) && argv(2))
- {
- makevectors(self.v_angle);
- WarpZone_TraceLine(self.origin + self.view_ofs, self.origin + self.view_ofs + v_forward * 100, MOVE_NORMAL, self);
-
- if(!autocvar_g_monsters_edit) { sprint(self, "Monster property editing is not enabled.\n"); return; }
- if(trace_ent.flags & FL_MONSTER)
- {
- if(trace_ent.realowner != self) { sprint(self, "That monster does not belong to you.\n"); return; }
- switch(argv(1))
- {
- case "skin":
- {
- if(trace_ent.monsterid != MON_MAGE)
- trace_ent.skin = stof(argv(2));
- return;
- }
- case "movetarget":
- {
- trace_ent.monster_moveflags = stof(argv(2));
- return;
- }
- }
- }
- }
- }
- default:
- sprint(self, "Incorrect parameters for ^2mobedit^7\n");
- case CMD_REQUEST_USAGE:
- {
- sprint(self, "\nUsage:^3 cmd mobedit [argument]\n");
- sprint(self, " Where 'argument' can be skin or movetarget.\n");
- sprint(self, " Aim at your monster to edit its properties.\n");
- return;
- }
- }
-}
-
-void ClientCommand_mobkill(float request)
-{
- switch(request)
- {
- case CMD_REQUEST_COMMAND:
- {
- makevectors(self.v_angle);
- WarpZone_TraceLine(self.origin + self.view_ofs, self.origin + self.view_ofs + v_forward * 100, MOVE_NORMAL, self);
-
- if(trace_ent.flags & FL_MONSTER)
- {
- if(trace_ent.realowner != self)
- {
- sprint(self, "That monster does not belong to you.\n");
- return;
- }
- sprint(self, strcat("Your pet '", trace_ent.monster_name, "' has been brutally mutilated.\n"));
- Damage (trace_ent, world, world, trace_ent.health + trace_ent.max_health + 200, DEATH_KILL, trace_ent.origin, '0 0 0');
- return;
- }
- }
-
- default:
- sprint(self, "Incorrect parameters for ^2mobkill^7\n");
- case CMD_REQUEST_USAGE:
- {
- sprint(self, "\nUsage:^3 cmd mobkill\n");
- sprint(self, " Aim at your monster to kill it.\n");
- return;
- }
- }
-}
-
-void ClientCommand_mobspawn(float request, float argc)
-{
- switch(request)
- {
- case CMD_REQUEST_COMMAND:
- {
- entity e;
- string tospawn;
- float moveflag, monstercount = 0;
-
- moveflag = (argv(2) ? stof(argv(2)) : 1); // follow owner if not defined
- tospawn = strtolower(argv(1));
-
- if(tospawn == "list")
- {
- sprint(self, monsterlist_reply);
- return;
- }
-
- FOR_EACH_MONSTER(e)
- {
- if(e.realowner == self)
- ++monstercount;
- }
-
- if(autocvar_g_monsters_max <= 0 || autocvar_g_monsters_max_perplayer <= 0) { sprint(self, "Monster spawning is disabled.\n"); return; }
- else if(!IS_PLAYER(self)) { sprint(self, "You can't spawn monsters while spectating.\n"); return; }
- else if(MUTATOR_CALLHOOK(AllowMobSpawning)) { sprint(self, "Monster spawning is currently disabled by a mutator.\n"); return; }
- else if(!autocvar_g_monsters) { Send_Notification(NOTIF_ONE, self, MSG_INFO, INFO_MONSTERS_DISABLED); return; }
- else if(self.vehicle) { sprint(self, "You can't spawn monsters while driving a vehicle.\n"); return; }
- else if(self.frozen) { sprint(self, "You can't spawn monsters while frozen.\n"); return; }
- else if(autocvar_g_campaign) { sprint(self, "You can't spawn monsters in campaign mode.\n"); return; }
- else if(self.deadflag != DEAD_NO) { sprint(self, "You can't spawn monsters while dead.\n"); return; }
- else if(monstercount >= autocvar_g_monsters_max_perplayer) { sprint(self, "You have spawned too many monsters, kill some before trying to spawn any more.\n"); return; }
- else if(totalspawned >= autocvar_g_monsters_max) { sprint(self, "The global maximum monster count has been reached, kill some before trying to spawn any more.\n"); return; }
- else if(tospawn != "")
- {
- float found = 0, i;
- entity mon;
-
- for(i = MON_FIRST; i <= MON_LAST; ++i)
- {
- mon = get_monsterinfo(i);
- if(mon.netname == tospawn)
- {
- found = true;
- break;
- }
- }
-
- if(found || tospawn == "random")
- {
- totalspawned += 1;
-
- makevectors(self.v_angle);
- WarpZone_TraceBox (CENTER_OR_VIEWOFS(self), PL_MIN, PL_MAX, CENTER_OR_VIEWOFS(self) + v_forward * 150, true, self);
- //WarpZone_TraceLine(self.origin + self.view_ofs, self.origin + self.view_ofs + v_forward * 150, MOVE_NORMAL, self);
-
- e = spawnmonster(tospawn, 0, self, self, trace_endpos, false, false, moveflag);
-
- sprint(self, strcat("Spawned ", e.monster_name, "\n"));
-
- return;
- }
- }
- }
-
- default:
- sprint(self, "Incorrect parameters for ^2mobspawn^7\n");
- case CMD_REQUEST_USAGE:
- {
- sprint(self, "\nUsage:^3 cmd mobspawn <random> <monster> [movetype]\n");
- sprint(self, " See 'cmd mobspawn list' for available monsters.\n");
- sprint(self, " Argument 'random' spawns a random monster.\n");
- sprint(self, " Monster will follow the owner if second argument is not defined.\n");
- return;
- }
- }
-}
-
void ClientCommand_physics(float request, float argc)
{
switch(request)
CLIENT_COMMAND("clientversion", ClientCommand_clientversion(request, arguments), "Release version of the game") \
CLIENT_COMMAND("mv_getpicture", ClientCommand_mv_getpicture(request, arguments), "Retrieve mapshot picture from the server") \
CLIENT_COMMAND("join", ClientCommand_join(request), "Become a player in the game") \
- CLIENT_COMMAND("mobedit", ClientCommand_mobedit(request, arguments), "Edit your monster's properties") \
- CLIENT_COMMAND("mobkill", ClientCommand_mobkill(request), "Kills your monster") \
- CLIENT_COMMAND("mobspawn", ClientCommand_mobspawn(request, arguments), "Spawn monsters infront of yourself") \
CLIENT_COMMAND("physics", ClientCommand_physics(request, arguments), "Change physics set") \
CLIENT_COMMAND("ready", ClientCommand_ready(request), "Qualify as ready to end warmup stage (or restart server if allowed)") \
CLIENT_COMMAND("say", ClientCommand_say(request, arguments, command), "Print a message to chat to all players") \
}
}
+void CommonCommand_editmob(float request, entity caller, float argc)
+{
+ switch(request)
+ {
+ case CMD_REQUEST_COMMAND:
+ {
+ if(autocvar_g_campaign) { print_to(caller, "Monster editing is disabled in singleplayer"); return; }
+ // no checks for g_monsters here, as it may be toggled mid match which existing monsters
+
+ if(caller)
+ {
+ makevectors(self.v_angle);
+ WarpZone_TraceLine(self.origin + self.view_ofs, self.origin + self.view_ofs + v_forward * 100, MOVE_NORMAL, self);
+ }
+
+ entity mon = trace_ent;
+ float is_visible = (mon.flags & FL_MONSTER);
+ string argument = argv(2);
+
+ switch(argv(1))
+ {
+ case "name":
+ {
+ if(!caller) { print_to(caller, "Only players can edit monsters"); return; }
+ if(!argument) { break; } // escape to usage
+ if(!autocvar_g_monsters_edit) { print_to(caller, "Monster editing is disabled"); return; }
+ if(mon.realowner != caller && autocvar_g_monsters_edit < 2) { print_to(caller, "This monster does not belong to you"); return; }
+ if(!is_visible) { print_to(caller, "You must look at your monster to edit it"); return; }
+
+ string mon_oldname = mon.monster_name;
+
+ mon.monster_name = argument;
+ if(mon.sprite) { WaypointSprite_UpdateSprites(mon.sprite, strzone(mon.monster_name), string_null, string_null); }
+ print_to(caller, sprintf("Your pet '%s' is now known as '%s'", mon_oldname, mon.monster_name));
+ return;
+ }
+ case "spawn":
+ {
+ if(!caller) { print_to(caller, "Only players can spawn monsters"); return; }
+ if(!argv(2)) { break; } // escape to usage
+
+ float moveflag, tmp_moncount = 0;
+ string arg_lower = strtolower(argument);
+ moveflag = (argv(3)) ? stof(argv(3)) : 1; // follow owner if not defined
+ ret_string = "Monster spawning is currently disabled by a mutator";
+
+ if(arg_lower == "list") { print_to(caller, monsterlist_reply); return; }
+
+ FOR_EACH_MONSTER(mon) { if(mon.realowner == caller) ++tmp_moncount; }
+
+ if(!autocvar_g_monsters) { print_to(caller, "Monsters are disabled"); return; }
+ if(autocvar_g_monsters_max <= 0 || autocvar_g_monsters_max_perplayer <= 0) { print_to(caller, "Monster spawning is disabled"); return; }
+ if(!IS_PLAYER(caller)) { print_to(caller, "You must be playing to spawn a monster"); return; }
+ if(MUTATOR_CALLHOOK(AllowMobSpawning)) { print_to(caller, ret_string); return; }
+ if(caller.vehicle) { print_to(caller, "You can't spawn monsters while driving a vehicle"); return; }
+ if(caller.frozen) { print_to(caller, "You can't spawn monsters while frozen"); return; }
+ if(caller.deadflag != DEAD_NO) { print_to(caller, "You can't spawn monsters while dead"); return; }
+ if(tmp_moncount >= autocvar_g_monsters_max) { print_to(caller, "The maximum monster count has been reached"); return; }
+ if(tmp_moncount >= autocvar_g_monsters_max_perplayer) { print_to(caller, "You can't spawn any more monsters"); return; }
+
+ float i = 0, found = false;
+ for(i = MON_FIRST; i <= MON_LAST; ++i)
+ {
+ mon = get_monsterinfo(i);
+ if(mon.netname == arg_lower) { found = true; break; }
+ }
+
+ if(!found && arg_lower != "random") { print_to(caller, "Invalid monster"); return; }
+
+ totalspawned += 1;
+ WarpZone_TraceBox (CENTER_OR_VIEWOFS(caller), caller.mins, caller.maxs, CENTER_OR_VIEWOFS(caller) + v_forward * 150, true, caller);
+ mon = spawnmonster(arg_lower, 0, caller, caller, trace_endpos, false, false, moveflag);
+ print_to(caller, strcat("Spawned ", mon.monster_name));
+ return;
+ }
+ case "kill":
+ {
+ if(!caller) { print_to(caller, "Only players can kill monsters"); return; }
+ if(mon.realowner != caller && autocvar_g_monsters_edit < 2) { print_to(caller, "This monster does not belong to you"); return; }
+ if(!is_visible) { print_to(caller, "You must look at your monster to edit it"); return; }
+
+ Damage (mon, world, world, mon.health + mon.max_health + 200, DEATH_KILL, mon.origin, '0 0 0');
+ print_to(caller, strcat("Your pet '", mon.monster_name, "' has been brutally mutilated"));
+ return;
+ }
+ case "skin":
+ {
+ if(!caller) { print_to(caller, "Only players can edit monsters"); return; }
+ if(!argument) { break; } // escape to usage
+ if(!autocvar_g_monsters_edit) { print_to(caller, "Monster editing is disabled"); return; }
+ if(!is_visible) { print_to(caller, "You must look at your monster to edit it"); return; }
+ if(mon.realowner != caller && autocvar_g_monsters_edit < 2) { print_to(caller, "This monster does not belong to you"); return; }
+ if(mon.monsterid == MON_MAGE) { print_to(caller, "Mage skins can't be changed"); return; } // TODO
+
+ mon.skin = stof(argument);
+ print_to(caller, strcat("Monster skin successfully changed to ", ftos(mon.skin)));
+ return;
+ }
+ case "movetarget":
+ {
+ if(!caller) { print_to(caller, "Only players can edit monsters"); return; }
+ if(!argument) { break; } // escape to usage
+ if(!autocvar_g_monsters_edit) { print_to(caller, "Monster editing is disabled"); return; }
+ if(!is_visible) { print_to(caller, "You must look at your monster to edit it"); return; }
+ if(mon.realowner != caller && autocvar_g_monsters_edit < 2) { print_to(caller, "This monster does not belong to you"); return; }
+
+ mon.monster_moveflags = stof(argument);
+ print_to(caller, strcat("Monster move target successfully changed to ", ftos(mon.monster_moveflags)));
+ return;
+ }
+ case "butcher":
+ {
+ if(caller) { print_to(caller, "This command is not available to players"); return; }
+ if(g_invasion) { print_to(caller, "This command does not work during an invasion!"); return; }
+
+ float tmp_remcount = 0;
+ entity tmp_entity;
+
+ FOR_EACH_MONSTER(tmp_entity) { Monster_Remove(tmp_entity); ++tmp_remcount; }
+
+ monsters_total = monsters_killed = totalspawned = 0;
+
+ print_to(caller, (tmp_remcount) ? sprintf("Killed %d monster%s", tmp_remcount, (tmp_remcount == 1) ? "" : "s") : "No monsters to kill");
+ return;
+ }
+ }
+ }
+
+ default:
+ case CMD_REQUEST_USAGE:
+ {
+ print_to(caller, strcat("\nUsage:^3 ", GetCommandPrefix(caller), " editmob command [arguments]"));
+ print_to(caller, " Where 'command' can be butcher spawn skin movetarget kill name");
+ print_to(caller, " spawn, skin, movetarget and name require 'arguments'");
+ print_to(caller, " spawn also takes arguments list and random");
+ print_to(caller, " Monster will follow owner if third argument of spawn command is not defined");
+ return;
+ }
+ }
+}
+
void CommonCommand_info(float request, entity caller, float argc)
{
switch(request)
{
if(caller) { caller.allowed_timeouts -= 1; }
- bprint(GetCallerName(caller), " ^7called a timeout", (caller ? strcat(" (", ftos(caller.allowed_timeouts), " timeout(s) left)") : ""), "!\n"); // write a bprint who started the timeout (and how many they have left)
+ // write a bprint who started the timeout (and how many they have left)
+ bprint(GetCallerName(caller), " ^7called a timeout", (caller ? strcat(" (", ftos(caller.allowed_timeouts), " timeout(s) left)") : ""), "!\n");
timeout_status = TIMEOUT_LEADTIME;
timeout_caller = caller;
#define COMMAND_COMMON_H
#include "vote.qh"
+#include "../../common/monsters/spawn.qh"
#include "../../common/command/generic.qh"
#include "../../common/command/command.qh"
void CommonCommand_cvar_purechanges(float request, entity caller);
+void CommonCommand_editmob(float request, entity caller, float argc);
+
void CommonCommand_info(float request, entity caller, float argc);
void CommonCommand_ladder(float request, entity caller);
#define COMMON_COMMANDS(request,caller,arguments,command) \
COMMON_COMMAND("cvar_changes", CommonCommand_cvar_changes(request, caller), "Prints a list of all changed server cvars") \
COMMON_COMMAND("cvar_purechanges", CommonCommand_cvar_purechanges(request, caller), "Prints a list of all changed gameplay cvars") \
+ COMMON_COMMAND("editmob", CommonCommand_editmob(request, caller, arguments), "Modifies a monster or all monsters") \
COMMON_COMMAND("info", CommonCommand_info(request, caller, arguments), "Request for unique server information set up by admin") \
COMMON_COMMAND("ladder", CommonCommand_ladder(request, caller), "Get information about top players if supported") \
COMMON_COMMAND("lsmaps", CommonCommand_lsmaps(request, caller), "List maps which can be used with the current game mode") \
}
}
-void GameCommand_mobbutcher(float request)
-{
- switch(request)
- {
- case CMD_REQUEST_COMMAND:
- {
- if(autocvar_g_campaign) { print("This command doesn't work in campaign mode.\n"); return; }
- if(g_invasion) { print("This command doesn't work during an invasion.\n"); return; }
-
- float removed_count = 0;
- entity head;
-
- FOR_EACH_MONSTER(head)
- {
- monster_remove(head);
- ++removed_count;
- }
-
- monsters_total = 0; // reset stats?
- monsters_killed = 0;
-
- totalspawned = 0;
-
- if(removed_count <= 0)
- print("No monsters to kill\n");
- else
- printf("Killed %d monster%s\n", removed_count, ((removed_count == 1) ? "" : "s"));
-
- return; // never fall through to usage
- }
-
- default:
- case CMD_REQUEST_USAGE:
- {
- print("\nUsage:^3 sv_cmd mobbutcher\n");
- print(" No arguments required.\n");
- return;
- }
- }
-}
-
void GameCommand_allready(float request)
{
switch(request)
// Do not hard code aliases for these, instead create them in commands.cfg... also: keep in alphabetical order, please ;)
#define SERVER_COMMANDS(request,arguments,command) \
SERVER_COMMAND("adminmsg", GameCommand_adminmsg(request, arguments), "Send an admin message to a client directly") \
- SERVER_COMMAND("mobbutcher", GameCommand_mobbutcher(request), "Instantly removes all monsters on the map") \
SERVER_COMMAND("allready", GameCommand_allready(request), "Restart the server and reset the players") \
SERVER_COMMAND("allspec", GameCommand_allspec(request, arguments), "Force all players to spectate") \
SERVER_COMMAND("anticheat", GameCommand_anticheat(request, arguments), "Create an anticheat report for a client") \
// INPUT:
// entity frag_attacker;
-MUTATOR_HOOKABLE(MonsterRespawn);
- // called when a monster wants to respawn
- // INPUT:
-// entity other;
+MUTATOR_HOOKABLE(MonsterRemove);
+ // called when a monster is being removed
+ // returning true hides removal effect
MUTATOR_HOOKABLE(MonsterDropItem);
// called when a monster is dropping loot
if(round_handler_GetEndTime() > 0 && round_handler_GetEndTime() - time <= 0)
{
FOR_EACH_MONSTER(head)
- monster_remove(head);
+ Monster_Remove(head);
Send_Notification(NOTIF_ALL, world, MSG_CENTER, CENTER_ROUND_OVER);
Send_Notification(NOTIF_ALL, world, MSG_INFO, INFO_ROUND_OVER);
}
FOR_EACH_MONSTER(head)
- monster_remove(head);
+ Monster_Remove(head);
if(teamplay)
{
self.monster_skill = inv_monsterskill;
if((get_monsterinfo(self.monsterid)).spawnflags & MON_FLAG_SUPERMONSTER)
- Send_Notification(NOTIF_ALL, world, MSG_CENTER, CENTER_INVASION_SUPERMONSTER, M_NAME(self.monsterid));
+ Send_Notification(NOTIF_ALL, world, MSG_CENTER, CENTER_INVASION_SUPERMONSTER, self.monster_name);
self.dphitcontentsmask = DPCONTENTS_SOLID | DPCONTENTS_BODY | DPCONTENTS_BOTCLIP | DPCONTENTS_MONSTERCLIP;