#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;
++bool monsters_animoverride(entity e)
++{
++ int monster_id = 0;
++ for(int i = MON_FIRST; i <= MON_LAST; ++i)
++ {
++ entity mon = get_monsterinfo(i);
++
++ //if(substring(e.model, 0, strlen(mon.model) - 4) == substring(mon.model, 0, strlen(mon.model) - 4))
++ if(e.model == mon.model)
++ {
++ monster_id = i;
++ break;
++ }
++ }
++
++ if(!monster_id) { return false; }
++
++ MON_ACTION(monster_id, MR_ANIM);
++
++ vector none = '0 0 0';
++ e.anim_duckwalk = e.anim_walk;
++ e.anim_duckjump = animfixfps(e, '5 1 10', none);
++ e.anim_duckidle = e.anim_idle;
++ e.anim_jump = animfixfps(e, '8 1 10', none);
++ e.anim_taunt = animfixfps(e, '12 1 0.33', none);
++ e.anim_runbackwards = e.anim_run;
++ e.anim_strafeleft = e.anim_run;
++ e.anim_straferight = e.anim_run;
++ e.anim_forwardright = e.anim_run;
++ e.anim_forwardleft = e.anim_run;
++ e.anim_backright = e.anim_run;
++ e.anim_backleft = e.anim_run;
++ e.anim_duckwalkbackwards = e.anim_walk;
++ e.anim_duckwalkstrafeleft = e.anim_walk;
++ e.anim_duckwalkstraferight = e.anim_walk;
++ e.anim_duckwalkforwardright = e.anim_walk;
++ e.anim_duckwalkforwardleft = e.anim_walk;
++ e.anim_duckwalkbackright = e.anim_walk;
++ e.anim_duckwalkbackleft = e.anim_walk;
++
++ // these anims ought to stay until stopped explicitly by weaponsystem
++ e.anim_shoot_z = 0.001;
++ e.anim_melee_z = 0.001;
++
++ return true;
++}
+
void animdecide_load_if_needed(entity e)
{
if(e.modelindex == e.animdecide_modelindex)
return;
e.animdecide_modelindex = e.modelindex;
++ if(substring(e.model, 0, 16) == "models/monsters/")
++ {
++ if(monsters_animoverride(e))
++ return;
++ }
++
vector none = '0 0 0';
e.anim_die1 = animfixfps(e, '0 1 0.5', none); // 2 seconds
e.anim_die2 = animfixfps(e, '1 1 0.5', none); // 2 seconds
- #include "monster/zombie.qc"
- #include "monster/spider.qc"
+#ifndef MENUQC
+#include "../animdecide.qh"
+vector animfixfps(entity e, vector a, vector b);
+#endif
+
#include "monster/mage.qc"
- #include "monster/wyvern.qc"
#include "monster/shambler.qc"
+ #include "monster/spider.qc"
+ #include "monster/wyvern.qc"
+ #include "monster/zombie.qc"
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
- entity get_monsterinfo(float id);
-
// special spawn flags
const int MONSTER_RESPAWN_DEATHPOINT = 16; // re-spawn where we died
const int MONSTER_TYPE_FLY = 32;
.string model; // full name of model
.int spawnflags;
.vector mins, maxs; // monster hitbox size
++.bool(int) monster_attackfunc;
+
+// 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(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; // 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,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,attackfunc,monsterflags,min_s,max_s,modelname,shortname,mname); \
- } \
- ACCUMULATE_FUNCTION(RegisterMonsters, RegisterMonsters_##id)
- #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,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"
-
- #undef REGISTER_MONSTER
- ACCUMULATE_FUNCTION(RegisterMonsters, register_monsters_done);
#endif
- #ifdef REGISTER_MONSTER
- REGISTER_MONSTER(
+ #ifndef MENUQC
-bool m_mage(int);
++bool M_Mage(int);
+ #endif
+ REGISTER_MONSTER_SIMPLE(
/* MON_##id */ 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",
/* netname */ "mage",
/* fullname */ _("Mage")
- );
+ ) {
+ #ifndef MENUQC
- this.monster_func = m_mage;
++ this.monster_func = M_Mage;
++ this.monster_func(MR_PRECACHE);
+ #endif
+ }
- #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;
}
else
{
- pointparticles(particleeffectnum("healing_fx"), head.origin, '0 0 0', 1);
+ Send_Effect("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);
}
}
}
}
-void mage_push()
+void M_Mage_Attack_Push()
{
- sound(self, CH_SHOTS, "weapons/tagexp1.wav", 1, ATTEN_NORM);
+ sound(self, CH_SHOTS, W_Sound("tagexp1"), 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);
+ Send_Effect("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);
}
return false;
}
- void spawnfunc_monster_mage() { Monster_Spawn(MON_MAGE); }
-void spawnfunc_monster_mage()
-{
- self.classname = "monster_mage";
-
- if(!monster_initialize(MON_MAGE.monsterid)) { remove(self); return; }
-}
++void spawnfunc_monster_mage() { Monster_Spawn(MON_MAGE.monsterid); }
-// 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)
{
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;
++ self.monster_attackfunc = M_Mage_Attack;
return true;
}
case MR_PRECACHE:
{
precache_model("models/monsters/mage.dpm");
- precache_sound ("weapons/grenade_impact.wav");
- precache_sound ("weapons/tagexp1.wav");
+ precache_sound (W_Sound("grenade_impact"));
+ precache_sound (W_Sound("tagexp1"));
return true;
}
+ #endif
}
return true;
}
--
- #endif // REGISTER_MONSTER
-#endif // SVQC
-#ifdef CSQC
-float m_mage(float req)
-{
- switch(req)
- {
- case MR_PRECACHE:
- {
- return true;
- }
- }
-
- return true;
-}
-
-#endif // CSQC
- #ifdef REGISTER_MONSTER
- REGISTER_MONSTER(
+ #ifndef MENUQC
-bool m_shambler(int);
++bool M_Shambler(int);
+ #endif
+ REGISTER_MONSTER_SIMPLE(
/* MON_##id */ 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",
/* netname */ "shambler",
/* fullname */ _("Shambler")
- );
+ ) {
+ #ifndef MENUQC
- this.monster_func = m_shambler;
++ this.monster_func = M_Shambler;
++ this.monster_func(MR_PRECACHE);
+ #endif
+ }
- #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 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);
+ Send_Effect("explosion_medium", (self.origin + (v_forward * 150)) - ('0 0 1' * self.maxs.z), '0 0 0', 1);
+ sound(self, CH_SHOTS, W_Sound("rocket_impact"), 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;
}
}
return false;
}
- void spawnfunc_monster_shambler() { Monster_Spawn(MON_SHAMBLER); }
-void spawnfunc_monster_shambler()
-{
- self.classname = "monster_shambler";
-
- if(!monster_initialize(MON_SHAMBLER.monsterid)) { remove(self); return; }
-}
++void spawnfunc_monster_shambler() { Monster_Spawn(MON_SHAMBLER.monsterid); }
+#endif // SVQC
-float m_shambler(float req)
+bool M_Shambler(int req)
{
switch(req)
{
{
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.weapon = WEP_ELECTRO; // matches attacks better than WEP_VORTEX
- self.monster_attackfunc = shambler_attack;
- self.frame = shambler_anim_stand;
- self.weapon = WEP_VORTEX.m_id;
++ self.weapon = WEP_ELECTRO.m_id; // 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;
++ self.monster_attackfunc = M_Shambler_Attack;
return true;
}
return true;
}
--
- #endif // REGISTER_MONSTER
-#endif // SVQC
-#ifdef CSQC
-float m_shambler(float req)
-{
- switch(req)
- {
- case MR_PRECACHE:
- {
- return true;
- }
- }
-
- return true;
-}
-
-#endif // CSQC
- #ifdef REGISTER_MONSTER
- REGISTER_MONSTER(
+ #ifndef MENUQC
-bool m_spider(int);
++bool M_Spider(int);
+ #endif
+ REGISTER_MONSTER_SIMPLE(
/* MON_##id */ 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",
/* netname */ "spider",
/* fullname */ _("Spider")
- );
+ ) {
+ #ifndef MENUQC
- this.monster_func = m_spider;
++ this.monster_func = M_Spider;
++ this.monster_func(MR_PRECACHE);
+ #endif
+ }
- #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;
CSQCProjectile(proj, true, PROJECTILE_ELECTRO, true);
}
- float M_Spider_Attack(float attack_type)
-float spider_attack(float attack_type)
++bool M_Spider_Attack(int attack_type)
{
switch(attack_type)
{
return false;
}
- void spawnfunc_monster_spider() { Monster_Spawn(MON_SPIDER); }
-void spawnfunc_monster_spider()
-{
- self.classname = "monster_spider";
-
- if(!monster_initialize(MON_SPIDER.monsterid)) { remove(self); return; }
-}
++void spawnfunc_monster_spider() { Monster_Spawn(MON_SPIDER.monsterid); }
+#endif // SVQC
-float m_spider(float req)
+bool M_Spider(int req)
{
switch(req)
{
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;
++ self.monster_attackfunc = M_Spider_Attack;
return true;
}
case MR_PRECACHE:
{
precache_model("models/monsters/spider.dpm");
- precache_sound ("weapons/electro_fire2.wav");
+ precache_sound (W_Sound("electro_fire2"));
return true;
}
+ #endif
}
return true;
}
--
- #endif // REGISTER_MONSTER
-#endif // SVQC
-#ifdef CSQC
-float m_spider(float req)
-{
- switch(req)
- {
- case MR_PRECACHE:
- {
- return true;
- }
- }
-
- return true;
-}
-
-#endif // CSQC
- #ifdef REGISTER_MONSTER
- REGISTER_MONSTER(
+ #ifndef MENUQC
-bool m_wyvern(int);
++bool M_Wyvern(int);
+ #endif
+ REGISTER_MONSTER_SIMPLE(
/* MON_##id */ 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",
/* netname */ "wyvern",
/* fullname */ _("Wyvern")
- );
+ ) {
+ #ifndef MENUQC
- this.monster_func = m_wyvern;
++ this.monster_func = M_Wyvern;
++ this.monster_func(MR_PRECACHE);
+ #endif
+ }
- #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;
return false;
}
- void spawnfunc_monster_wyvern() { Monster_Spawn(MON_WYVERN); }
-void spawnfunc_monster_wyvern()
-{
- self.classname = "monster_wyvern";
-
- if(!monster_initialize(MON_WYVERN.monsterid)) { remove(self); return; }
-}
--
-// compatibility with old spawns
-void spawnfunc_monster_wizard() { spawnfunc_monster_wyvern(); }
++void spawnfunc_monster_wyvern() { Monster_Spawn(MON_WYVERN.monsterid); }
+#endif // SVQC
-float m_wyvern(float req)
+bool M_Wyvern(int req)
{
switch(req)
{
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;
++ self.monster_attackfunc = M_Wyvern_Attack;
return true;
}
return true;
}
--
- #endif // REGISTER_MONSTER
-#endif // SVQC
-#ifdef CSQC
-float m_wyvern(float req)
-{
- switch(req)
- {
- case MR_PRECACHE:
- {
- return true;
- }
- }
-
- return true;
-}
-
-#endif // CSQC
- #ifdef REGISTER_MONSTER
- REGISTER_MONSTER(
+ #ifndef MENUQC
-bool m_zombie(int);
++bool M_Zombie(int);
+ #endif
+ REGISTER_MONSTER_SIMPLE(
/* MON_##id */ ZOMBIE,
- /* functions */ M_Zombie, M_Zombie_Attack,
/* spawnflags */ MON_FLAG_MELEE,
/* mins,maxs */ '-18 -18 -25', '18 18 47',
/* model */ "zombie.dpm",
/* netname */ "zombie",
/* fullname */ _("Zombie")
- );
+ ) {
+ #ifndef MENUQC
- this.monster_func = m_zombie;
++ this.monster_func = M_Zombie;
++ this.monster_func(MR_PRECACHE);
+ #endif
+ }
- #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;
return false;
}
- void spawnfunc_monster_zombie() { Monster_Spawn(MON_ZOMBIE); }
-void spawnfunc_monster_zombie()
-{
- self.classname = "monster_zombie";
-
- if(!monster_initialize(MON_ZOMBIE.monsterid)) { remove(self); return; }
-}
++void spawnfunc_monster_zombie() { Monster_Spawn(MON_ZOMBIE.monsterid); }
+#endif // SVQC
-float m_zombie(float req)
+bool M_Zombie(int req)
{
switch(req)
{
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.monster_attackfunc = M_Zombie_Attack;
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;
}
return true;
}
--
- #endif // REGISTER_MONSTER
-#endif // SVQC
-#ifdef CSQC
-float m_zombie(float req)
-{
- switch(req)
- {
- case MR_PRECACHE:
- {
- return true;
- }
- }
-
- return true;
-}
-
-#endif // CSQC
#include "../triggers/triggers.qh"
#include "../../csqcmodellib/sv_model.qh"
#include "../../server/round_handler.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(self.flags & FL_MONSTER)
- 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(IS_VEHICLE(targ))
- 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(!IS_VEHICLE(targ))
- if(targ.deadflag != DEAD_NO || ent.deadflag != DEAD_NO || targ.health <= 0 || ent.health <= 0)
- return false; // enemy/self is dead
++ if(IS_MONSTER(self))
+ {
+ 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(!IS_VEHICLE(targ))
- 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
++ || (IS_VEHICLE(player) && !((get_monsterinfo(mon.monsterid)).spawnflags & MON_FLAG_RANGED)) // melee vs vehicle is useless
+ || (time < game_starttime) // monsters do nothing before match has started
+ || (player.takedamage == DAMAGE_NO)
+ || (player.items & IT_INVISIBILITY)
+ || (IS_SPEC(player) || IS_OBSERVER(player)) // don't attack spectators
- || (!(player.vehicle_flags & VHF_ISVEHICLE) && (player.deadflag != DEAD_NO || mon.deadflag != DEAD_NO || player.health <= 0 || mon.health <= 0))
++ || (!IS_VEHICLE(player) && (player.deadflag != DEAD_NO || mon.deadflag != DEAD_NO || player.health <= 0 || mon.health <= 0))
+ || (mon.monster_follow == player || player.monster_follow == mon)
- || (!(player.vehicle_flags & VHF_ISVEHICLE) && (player.flags & FL_NOTARGET))
++ || (!IS_VEHICLE(player) && (player.flags & FL_NOTARGET))
+ || (!autocvar_g_monsters_typefrag && player.BUTTON_CHAT)
+ || (SAME_TEAM(player, mon))
+ || (player.frozen)
+ || (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;
return string_null;
}
- float Monster_Sounds_Load(string f, float first)
-float LoadMonsterSounds(string f, float first)
++bool Monster_Sounds_Load(string f, int first)
{
float fh;
string s;
fh = fopen(f, FILE_READ);
if(fh < 0)
{
- dprint("Monster sound file not found: ", f, "\n");
+ LOG_TRACE("Monster sound file not found: ", f, "\n");
- return 0;
+ return false;
}
while((s = fgets(fh)))
{
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
+
+ vector old = self.velocity;
+
+ self.velocity = vel;
+ tracetoss(self, self);
+ self.velocity = old;
+ if (trace_ent != self.enemy)
+ return false;
+
+ return true;
+}
+
+bool Monster_Attack_Leap(vector anm, void() touchfunc, vector vel, float animtime)
+{
+ if(!Monster_Attack_Leap_Check(vel))
+ return false;
+
+ setanim(self, anm, false, true, false);
+
+ 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(dostop)
+ 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_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)
{
- self.velocity_x = 0;
- self.velocity_y = 0;
- self.state = MONSTER_STATE_ATTACK_MELEE;
+ 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;
}
- self.frame = anim;
+ 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;
+ }
+}
- if(anim_finished != 0)
- self.attack_finished_single = time + anim_finished;
- monster_makevectors(targ);
+// ======================
+// Main monster functions
+// ======================
- traceline(self.origin + self.view_ofs, self.origin + v_forward * er, 0, self);
++void Monster_UpdateModel()
++{
++ // assume some defaults
++ /*self.anim_idle = animfixfps(self, '0 1 0.01', '0 0 0');
++ self.anim_walk = animfixfps(self, '1 1 0.01', '0 0 0');
++ self.anim_run = animfixfps(self, '2 1 0.01', '0 0 0');
++ self.anim_fire1 = animfixfps(self, '3 1 0.01', '0 0 0');
++ self.anim_fire2 = animfixfps(self, '4 1 0.01', '0 0 0');
++ self.anim_melee = animfixfps(self, '5 1 0.01', '0 0 0');
++ self.anim_pain1 = animfixfps(self, '6 1 0.01', '0 0 0');
++ self.anim_pain2 = animfixfps(self, '7 1 0.01', '0 0 0');
++ self.anim_die1 = animfixfps(self, '8 1 0.01', '0 0 0');
++ self.anim_die2 = animfixfps(self, '9 1 0.01', '0 0 0');*/
++
++ // then get the real values
++ MON_ACTION(self.monsterid, MR_ANIM);
++}
+
- if(trace_ent.takedamage)
- Damage(trace_ent, self, self, damg * Monster_SkillModifier(), deathtype, trace_ent.origin, normalize(trace_ent.origin - self.origin));
+void Monster_Touch()
+{
+ if(other == world) { return; }
- return true;
+ if(other.monster_attack)
+ if(self.enemy != other)
- if(!(self.flags & FL_MONSTER))
++ if(!IS_MONSTER(other))
+ if(Monster_ValidTarget(self, other))
+ self.enemy = other;
}
-void Monster_CheckMinibossFlag ()
+void Monster_Miniboss_Check()
{
if(MUTATOR_CALLHOOK(MonsterCheckBossFlag))
return;
}
}
- float Monster_Respawn_Check()
-float Monster_CanRespawn(entity ent)
++bool Monster_Respawn_Check()
{
- if(self.spawnflags & MONSTERFLAG_NORESPAWN) { return false; }
- if(!autocvar_g_monsters_respawn) { return false; }
- other = ent;
- if(ent.deadflag == DEAD_DEAD) // don't call when monster isn't dead
- if(MUTATOR_CALLHOOK(MonsterRespawn, ent))
++ if(self.deadflag == DEAD_DEAD) // don't call when monster isn't dead
++ if(MUTATOR_CALLHOOK(MonsterRespawn, self))
+ return true; // enabled by a mutator
+
- if(ent.spawnflags & MONSTERFLAG_NORESPAWN)
++ if(self.spawnflags & MONSTERFLAG_NORESPAWN)
+ return false;
+
+ if(!autocvar_g_monsters_respawn)
+ return false;
return true;
}
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
-
- Send_Effect("item_pickup", mon.origin, '0 0 0', 1);
+ if(!mon) { return; }
- 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);
++ if(!MUTATOR_CALLHOOK(MonsterRemove, mon))
++ Send_Effect("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_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; }
+ float reverse = FALSE;
+ vector a, b;
+
+ makevectors(self.angles);
+ a = self.origin + '0 0 16';
+ b = self.origin + '0 0 16' + v_forward * 32;
+
+ traceline(a, b, MOVE_NORMAL, self);
+
+ if(trace_fraction != 1.0)
+ {
+ reverse = TRUE;
+
+ if(trace_ent)
+ if(IS_PLAYER(trace_ent) && !(trace_ent.items & IT_STRENGTH))
+ reverse = FALSE;
+ }
+
+ // TODO: fix this... tracing is broken if the floor is thin
+ /*
+ if(!allow_jumpoff)
+ {
+ 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);
-
+
- ent.team = newteam;
- ent.monster_attack = true; // new team, activate attacking
- monster_setupcolors(ent);
+ 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);
+}
- if(ent.sprite)
++void Monster_Anim()
++{
++ int deadbits = (self.anim_state & (ANIMSTATE_DEAD1 | ANIMSTATE_DEAD2));
++ if(self.deadflag)
+ {
- WaypointSprite_UpdateTeamRadar(ent.sprite, RADARICON_DANGER, ((newteam) ? Team_ColorRGB(newteam) : '1 0 0'));
-
- ent.sprite.team = newteam;
- ent.sprite.SendFlags |= 1;
++ if (!deadbits)
++ {
++ // Decide on which death animation to use.
++ if(random() < 0.5)
++ deadbits = ANIMSTATE_DEAD1;
++ else
++ deadbits = ANIMSTATE_DEAD2;
++ }
++ }
++ else
++ {
++ // Clear a previous death animation.
++ deadbits = 0;
++ }
++ int animbits = deadbits;
++ if(self.frozen)
++ animbits |= ANIMSTATE_FROZEN;
++ if(self.crouch)
++ animbits |= ANIMSTATE_DUCK; // not that monsters can crouch currently...
++ animdecide_setstate(self, animbits, false);
++ animdecide_setimplicitstate(self, (self.flags & FL_ONGROUND));
++
++ /* // weapon entities for monsters?
++ if (self.weaponentity)
++ {
++ updateanim(self.weaponentity);
++ if (!self.weaponentity.animstate_override)
++ setanim(self.weaponentity, self.weaponentity.anim_idle, true, false, false);
+ }
++ */
+ }
+
-void monster_think()
+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);
+
++ Monster_Anim();
+
CSQCMODEL_AUTOUPDATE();
}
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);
- entity wp = WaypointSprite_Spawn(WP_Monster, 0, 1024, self, '0 0 1' * (self.maxs.z + 15), world, self.team, self, sprite, true, RADARICON_DANGER);
- wp.wp_extra = self.monsterid;
- wp.colormod = ((self.team) ? Team_ColorRGB(self.team) : '1 0 0');
- if(!(self.spawnflags & MONSTERFLAG_INVINCIBLE))
+ if(autocvar_g_monsters_healthbars)
{
- WaypointSprite_Spawn(self.monster_name, 0, 1024, self, '0 0 1' * (self.maxs_z + 15), world, self.team,
- self, sprite, true, RADARICON_DANGER, ((self.team) ? Team_ColorRGB(self.team) : '1 0 0'));
-
- WaypointSprite_UpdateMaxHealth(self.sprite, self.max_health);
- WaypointSprite_UpdateHealth(self.sprite, self.health);
++ entity wp = WaypointSprite_Spawn(WP_Monster, 0, 1024, self, '0 0 1' * (self.maxs.z + 15), world, self.team, self, sprite, true, RADARICON_DANGER);
++ wp.wp_extra = self.monsterid;
++ wp.colormod = ((self.team) ? Team_ColorRGB(self.team) : '1 0 0');
+ if(!(self.spawnflags & MONSTERFLAG_INVINCIBLE))
+ {
+ WaypointSprite_UpdateMaxHealth(self.sprite, self.max_health);
+ WaypointSprite_UpdateHealth(self.sprite, self.health);
+ }
}
- self.think = monster_think;
+ self.think = Monster_Think;
self.nextthink = time + self.ticrate;
if(MUTATOR_CALLHOOK(MonsterSpawn))
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, 60);
-
- if(!self.m_armor_blockpercent)
- self.m_armor_blockpercent = 0.5;
-
- if(!self.target_range)
- self.target_range = autocvar_g_monsters_target_range;
+ self.ticrate = bound(sys_frametime, ((!self.ticrate) ? autocvar_g_monsters_think_delay : self.ticrate), 60);
- if(!self.respawntime)
- self.respawntime = autocvar_g_monsters_respawn_delay;
++ Monster_UpdateModel();
+
- if(!self.monster_moveflags)
- self.monster_moveflags = MONSTER_MOVE_WANDER;
+ if(!Monster_Spawn_Setup())
+ {
+ Monster_Remove(self);
+ return false;
+ }
if(!self.noalign)
{
}
}
- void CommonCommand_editmob(float request, entity caller, float argc)
++void CommonCommand_editmob(int request, entity caller, int 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);
++ bool is_visible = IS_MONSTER(mon);
+ 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); }
++ if(mon.sprite) { WaypointSprite_UpdateSprites(mon.sprite, WP_Monster, WP_Null, WP_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;
++ int 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)
++ bool found = false;
++ for(int 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
++ if(mon.monsterid == MON_MAGE.monsterid) { 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;
++ int 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)
--- /dev/null
+ #ifndef SERVER_MUTATORS_EVENTS_H
+ #define SERVER_MUTATORS_EVENTS_H
+
+ #include "../../common/mutators/base.qh"
+
+ // register all possible hooks here
+
+ /** called when a player becomes observer, after shared setup */
+ #define EV_MakePlayerObserver(i, o) \
+ /**/
+ MUTATOR_HOOKABLE(MakePlayerObserver, EV_MakePlayerObserver)
+
+ /** */
+ #define EV_PutClientInServer(i, o) \
+ /** client wanting to spawn */ i(entity, self) \
+ /**/
+ MUTATOR_HOOKABLE(PutClientInServer, EV_PutClientInServer);
+
+ /** called when a player spawns as player, after shared setup, before his weapon is chosen (so items may be changed in here) */
+ #define EV_PlayerSpawn(i, o) \
+ /** spot that was used, or world */ i(entity, spawn_spot) \
+ /**/
+ entity spawn_spot;
+ MUTATOR_HOOKABLE(PlayerSpawn, EV_PlayerSpawn);
+
+ /** called in reset_map */
+ #define EV_reset_map_global(i, o) \
+ /**/
+ MUTATOR_HOOKABLE(reset_map_global, EV_reset_map_global);
+
+ /** called in reset_map */
+ #define EV_reset_map_players(i, o) \
+ /**/
+ MUTATOR_HOOKABLE(reset_map_players, EV_reset_map_players);
+
+ /** returns 1 if clearing player score shall not be allowed */
+ #define EV_ForbidPlayerScore_Clear(i, o) \
+ /**/
+ MUTATOR_HOOKABLE(ForbidPlayerScore_Clear, EV_ForbidPlayerScore_Clear);
+
+ /** called when a player disconnects */
+ #define EV_ClientDisconnect(i, o) \
+ /**/
+ MUTATOR_HOOKABLE(ClientDisconnect, EV_ClientDisconnect);
+
+ /** called when a player dies to e.g. remove stuff he was carrying. */
+ #define EV_PlayerDies(i, o) \
+ /**/ i(entity, frag_inflictor) \
+ /**/ i(entity, frag_attacker) \
+ /** same as self */ i(entity, frag_target) \
+ /**/ i(int, frag_deathtype) \
+ /**/
+ entity frag_inflictor;
+ entity frag_attacker;
+ entity frag_target;
+ int frag_deathtype;
+ MUTATOR_HOOKABLE(PlayerDies, EV_PlayerDies);
+
+ /** called when a player dies to e.g. remove stuff he was carrying */
+ #define EV_PlayHitsound(i, o) \
+ /**/ i(entity, frag_victim) \
+ /**/
+ entity frag_victim;
+ MUTATOR_HOOKABLE(PlayHitsound, EV_PlayHitsound);
+
+ /** called when a weapon sound is about to be played, allows custom paths etc. */
+ #define EV_WeaponSound(i, o) \
+ /**/ i(string, weapon_sound) \
+ /**/ i(string, weapon_sound_output) \
+ /**/ o(string, weapon_sound_output) \
+ /**/
+ string weapon_sound;
+ string weapon_sound_output;
+ MUTATOR_HOOKABLE(WeaponSound, EV_WeaponSound);
+
+ /** called when a weapon model is about to be set, allows custom paths etc. */
+ #define EV_WeaponModel(i, o) \
+ /**/ i(string, weapon_model) \
+ /**/ i(string, weapon_model_output) \
+ /**/ o(string, weapon_model_output) \
+ /**/
+ string weapon_model;
+ string weapon_model_output;
+ MUTATOR_HOOKABLE(WeaponModel, EV_WeaponModel);
+
+ /** called when an item model is about to be set, allows custom paths etc. */
+ #define EV_ItemModel(i, o) \
+ /**/ i(string, item_model) \
+ /**/ i(string, item_model_output) \
+ /**/ o(string, item_model_output) \
+ /**/
+ string item_model;
+ string item_model_output;
+ MUTATOR_HOOKABLE(ItemModel, EV_ItemModel);
+
+ /** called when a player presses the jump key */
+ #define EV_PlayerJump(i, o) \
+ /**/ i(float, player_multijump) \
+ /**/ i(float, player_jumpheight) \
+ /**/ o(float, player_multijump) \
+ /**/ o(float, player_jumpheight) \
+ /**/
+ float player_multijump;
+ float player_jumpheight;
+ MUTATOR_HOOKABLE(PlayerJump, EV_PlayerJump);
+
+ /** called when someone was fragged by "self", and is expected to change frag_score to adjust scoring for the kill */
+ #define EV_GiveFragsForKill(i, o) \
+ /** same as self */ i(entity, frag_attacker) \
+ /**/ i(entity, frag_target) \
+ /**/ i(float, frag_score) \
+ /**/ o(float, frag_score) \
+ /**/
+ float frag_score;
+ MUTATOR_HOOKABLE(GiveFragsForKill, EV_GiveFragsForKill);
+
+ /** called when the match ends */
+ MUTATOR_HOOKABLE(MatchEnd, EV_NO_ARGS);
+
+ /** should adjust ret_float to contain the team count */
+ #define EV_GetTeamCount(i, o) \
+ /**/ i(float, ret_float) \
+ /**/ o(float, ret_float) \
+ /**/
+ float ret_float;
+ MUTATOR_HOOKABLE(GetTeamCount, EV_GetTeamCount);
+
+ /** copies variables for spectating "other" to "self" */
+ #define EV_SpectateCopy(i, o) \
+ /**/ i(entity, other) \
+ /**/ i(entity, self) \
+ /**/
+ MUTATOR_HOOKABLE(SpectateCopy, EV_SpectateCopy);
+
+ /** called when formatting a chat message to replace fancy functions */
+ #define EV_FormatMessage(i, o) \
+ /**/ i(string, format_escape) \
+ /**/ i(string, format_replacement) \
+ /**/ o(string, format_replacement) \
+ /**/
+ string format_escape;
+ string format_replacement;
+ MUTATOR_HOOKABLE(FormatMessage, EV_FormatMessage);
+
+ /** returns 1 if throwing the current weapon shall not be allowed */
+ MUTATOR_HOOKABLE(ForbidThrowCurrentWeapon, EV_NO_ARGS);
+
+ /** allows changing attack rate */
+ #define EV_WeaponRateFactor(i, o) \
+ /**/ i(float, weapon_rate) \
+ /**/ o(float, weapon_rate) \
+ /**/
+ float weapon_rate;
+ MUTATOR_HOOKABLE(WeaponRateFactor, EV_WeaponRateFactor);
+
+ /** allows changing weapon speed (projectiles mostly) */
+ #define EV_WeaponSpeedFactor(i, o) \
+ /**/ i(float, ret_float) \
+ /**/ o(float, ret_float) \
+ /**/
+ MUTATOR_HOOKABLE(WeaponSpeedFactor, EV_WeaponSpeedFactor);
+
+ /** adjusts {warmup_}start_{items,weapons,ammo_{cells,plasma,rockets,nails,shells,fuel}} */
+ MUTATOR_HOOKABLE(SetStartItems, EV_NO_ARGS);
+
+ /** called every frame. customizes the waypoint for spectators */
+ #define EV_CustomizeWaypoint(i, o) \
+ /** waypoint */ i(entity, self) \
+ /** player; other.enemy = spectator */ i(entity, other) \
+ /**/
+ MUTATOR_HOOKABLE(CustomizeWaypoint, EV_CustomizeWaypoint);
+
+ /**
+ * checks if the current item may be spawned (self.items and self.weapons may be read and written to, as well as the ammo_ fields)
+ * return error to request removal
+ */
+ MUTATOR_HOOKABLE(FilterItem, EV_NO_ARGS);
+
+ /** return error to request removal */
+ #define EV_TurretSpawn(i, o) \
+ /** turret */ i(entity, self) \
+ /**/
+ MUTATOR_HOOKABLE(TurretSpawn, EV_TurretSpawn);
+
+ /** return error to not attack */
+ #define EV_TurretFire(i, o) \
+ /** turret */ i(entity, self) \
+ /**/
+ MUTATOR_HOOKABLE(TurretFire, EV_TurretFire);
+
+ /** return error to not attack */
+ #define EV_Turret_CheckFire(i, o) \
+ /**/ i(bool, ret_bool) \
+ /**/ o(bool, ret_bool) \
+ /**/
+ bool ret_bool;
+ MUTATOR_HOOKABLE(Turret_CheckFire, EV_Turret_CheckFire);
+
+ /** return error to prevent entity spawn, or modify the entity */
+ MUTATOR_HOOKABLE(OnEntityPreSpawn, EV_NO_ARGS);
+
+ /** runs in the event loop for players; is called for ALL player entities, also bots, also the dead, or spectators */
+ MUTATOR_HOOKABLE(PlayerPreThink, EV_NO_ARGS);
+
+ /** TODO change this into a general PlayerPostThink hook? */
+ MUTATOR_HOOKABLE(GetPressedKeys, EV_NO_ARGS);
+
+ /**
+ * called before any player physics, may adjust variables for movement,
+ * is run AFTER bot code and idle checking
+ */
+ MUTATOR_HOOKABLE(PlayerPhysics, EV_NO_ARGS);
+
+ /** is meant to call GetCvars_handle*(get_cvars_s, get_cvars_f, cvarfield, "cvarname") for cvars this mutator needs from the client */
+ #define EV_GetCvars(i, o) \
+ /**/ i(float, get_cvars_f) \
+ /**/ i(string, get_cvars_s) \
+ /**/
+ float get_cvars_f;
+ string get_cvars_s;
+ MUTATOR_HOOKABLE(GetCvars, EV_NO_ARGS); // NOTE: Can't use EV_GetCvars because of `SZ_GetSpace: overflow`
+
+ /** can edit any "just fired" projectile */
+ #define EV_EditProjectile(i, o) \
+ /**/ i(entity, self) \
+ /**/ i(entity, other) \
+ /**/
+ MUTATOR_HOOKABLE(EditProjectile, EV_EditProjectile);
+
+ /** called when a monster spawns */
+ MUTATOR_HOOKABLE(MonsterSpawn, EV_NO_ARGS);
+
+ /** called when a monster dies */
+ #define EV_MonsterDies(i, o) \
+ /**/ i(entity, frag_attacker) \
+ /**/
+ MUTATOR_HOOKABLE(MonsterDies, EV_MonsterDies);
+
++/** called when a monster dies */
++#define EV_MonsterRemove(i, o) \
++ /**/ i(entity, rem_mon) \
++ /**/
++entity rem_mon; // avoiding ovewriting self & other
++MUTATOR_HOOKABLE(MonsterRemove, EV_MonsterRemove);
++
+ /** called when a monster wants to respawn */
+ #define EV_MonsterRespawn(i, o) \
+ /**/ i(entity, other) \
+ /**/
+ MUTATOR_HOOKABLE(MonsterRespawn, EV_MonsterRespawn);
+
+ /** called when a monster is dropping loot */
+ #define EV_MonsterDropItem(i, o) \
+ /**/ i(entity, other) \
+ /**/ o(entity, other) \
+ /**/
+ .void() monster_loot;
+ MUTATOR_HOOKABLE(MonsterDropItem, EV_MonsterDropItem);
+
+ /**
+ * called when a monster moves
+ * returning true makes the monster stop
+ */
+ #define EV_MonsterMove(i, o) \
+ /**/ i(float, monster_speed_run) \
+ /**/ o(float, monster_speed_run) \
+ /**/ i(float, monster_speed_walk) \
+ /**/ o(float, monster_speed_walk) \
+ /**/ i(entity, monster_target) \
+ /**/
+ float monster_speed_run;
+ float monster_speed_walk;
+ entity monster_target;
+ MUTATOR_HOOKABLE(MonsterMove, EV_MonsterMove);
+
+ /** called when a monster looks for another target */
+ MUTATOR_HOOKABLE(MonsterFindTarget, EV_NO_ARGS);
+
+ /** called to change a random monster to a miniboss */
+ MUTATOR_HOOKABLE(MonsterCheckBossFlag, EV_NO_ARGS);
+
+ /**
+ * called when a player tries to spawn a monster
+ * return 1 to prevent spawning
+ */
+ MUTATOR_HOOKABLE(AllowMobSpawning, EV_NO_ARGS);
+
+ /** called when a player gets damaged to e.g. remove stuff he was carrying. */
+ #define EV_PlayerDamage_SplitHealthArmor(i, o) \
+ /**/ i(entity, frag_inflictor) \
+ /**/ i(entity, frag_attacker) \
+ /** same as self */ i(entity, frag_target) \
+ /** NOTE: this force already HAS been applied */ i(vector, damage_force) \
+ /**/ i(float, damage_take) \
+ /**/ o(float, damage_take) \
+ /**/ i(float, damage_save) \
+ /**/ o(float, damage_save) \
+ /**/
+ vector damage_force;
+ float damage_take;
+ float damage_save;
+ MUTATOR_HOOKABLE(PlayerDamage_SplitHealthArmor, EV_PlayerDamage_SplitHealthArmor);
+
+ /**
+ * called to adjust damage and force values which are applied to the player, used for e.g. strength damage/force multiplier
+ * i'm not sure if I should change this around slightly (Naming of the entities, and also how they're done in g_damage).
+ */
+ #define EV_PlayerDamage_Calculate(i, o) \
+ /**/ i(entity, frag_attacker) \
+ /**/ i(entity, frag_target) \
+ /**/ i(float, frag_deathtype) \
+ /**/ i(float, frag_damage) \
+ /**/ o(float, frag_damage) \
+ /**/ i(float, frag_mirrordamage) \
+ /**/ o(float, frag_mirrordamage) \
+ /**/ i(vector, frag_force) \
+ /**/ o(vector, frag_force) \
+ /**/
+ float frag_damage;
+ float frag_mirrordamage;
+ vector frag_force;
+ MUTATOR_HOOKABLE(PlayerDamage_Calculate, EV_PlayerDamage_Calculate);
+
+ /**
+ * Called when a player is damaged
+ */
+ #define EV_PlayerDamaged(i, o) \
+ /** attacker */ i(entity, mutator_argv_entity_0) \
+ /** target */ i(entity, mutator_argv_entity_1) \
+ /** health */ i(int, mutator_argv_int_0) \
+ /** armor */ i(int, mutator_argv_int_1) \
+ /** location */ i(vector, mutator_argv_vector_0) \
+ /**/
+ MUTATOR_HOOKABLE(PlayerDamaged, EV_PlayerDamaged);
+
+ /** called at the end of player_powerups() in cl_client.qc, used for manipulating the values which are set by powerup items. */
+ #define EV_PlayerPowerups(i, o) \
+ /**/ i(entity, self) \
+ /**/ i(int, olditems) \
+ /**/
+ int olditems;
+ MUTATOR_HOOKABLE(PlayerPowerups, EV_PlayerPowerups);
+
+ /**
+ * called every player think frame
+ * return 1 to disable regen
+ */
+ #define EV_PlayerRegen(i, o) \
+ /**/ i(float, regen_mod_max) \
+ /**/ o(float, regen_mod_max) \
+ /**/ i(float, regen_mod_regen) \
+ /**/ o(float, regen_mod_regen) \
+ /**/ i(float, regen_mod_rot) \
+ /**/ o(float, regen_mod_rot) \
+ /**/ i(float, regen_mod_limit) \
+ /**/ o(float, regen_mod_limit) \
+ /**/
+ float regen_mod_max;
+ float regen_mod_regen;
+ float regen_mod_rot;
+ float regen_mod_limit;
+ MUTATOR_HOOKABLE(PlayerRegen, EV_PlayerRegen);
+
+ /**
+ * called when the use key is pressed
+ * if MUTATOR_RETURNVALUE is 1, don't do anything
+ * return 1 if the use key actually did something
+ */
+ MUTATOR_HOOKABLE(PlayerUseKey, EV_NO_ARGS);
+
+ /**
+ * called when a client command is parsed
+ * NOTE: hooks MUST start with if (MUTATOR_RETURNVALUE) return false;
+ * NOTE: return true if you handled the command, return false to continue handling
+ * NOTE: THESE HOOKS MUST NEVER EVER CALL tokenize()
+ * // example:
+ * MUTATOR_HOOKFUNCTION(foo_SV_ParseClientCommand)
+ * {
+ * if (MUTATOR_RETURNVALUE) // command was already handled?
+ * return false;
+ * if (cmd_name == "echocvar" && cmd_argc >= 2)
+ * {
+ * print(cvar_string(argv(1)), "\n");
+ * return true;
+ * }
+ * if (cmd_name == "echostring" && cmd_argc >= 2)
+ * {
+ * print(substring(cmd_string, argv_start_index(1), argv_end_index(-1) - argv_start_index(1)), "\n");
+ * return true;
+ * }
+ * return false;
+ * }
+ */
+ #define EV_SV_ParseClientCommand(i, o) \
+ /** command name */ i(string, cmd_name) \
+ /** also, argv() can be used */ i(int, cmd_argc) \
+ /** whole command, use only if you really have to */ i(string, cmd_string) \
+ /**/
+ string cmd_name;
+ int cmd_argc;
+ string cmd_string;
+ MUTATOR_HOOKABLE(SV_ParseClientCommand, EV_SV_ParseClientCommand);
+
+ /**
+ * called when a spawnpoint is being evaluated
+ * return 1 to make the spawnpoint unusable
+ */
+ #define EV_Spawn_Score(i, o) \
+ /** player wanting to spawn */ i(entity, self) \
+ /** spot to be evaluated */ i(entity, spawn_spot) \
+ /** _x is priority, _y is "distance" */ i(vector, spawn_score) \
+ /**/ o(vector, spawn_score) \
+ /**/
+ vector spawn_score;
+ MUTATOR_HOOKABLE(Spawn_Score, EV_Spawn_Score);
+
+ /** runs globally each server frame */
+ MUTATOR_HOOKABLE(SV_StartFrame, EV_NO_ARGS);
+
+ #define EV_SetModname(i, o) \
+ /** name of the mutator/mod if it warrants showing as such in the server browser */ \
+ o(string, modname) \
+ /**/
+ MUTATOR_HOOKABLE(SetModname, EV_SetModname);
+
+ /**
+ * called for each item being spawned on a map, including dropped weapons
+ * return 1 to remove an item
+ */
+ #define EV_Item_Spawn(i, o) \
+ /** the item */ i(entity, self) \
+ /**/
+ MUTATOR_HOOKABLE(Item_Spawn, EV_Item_Spawn);
+
+ #define EV_SetWeaponreplace(i, o) \
+ /** map entity */ i(entity, self) \
+ /** weapon info */ i(entity, other) \
+ /**/ i(string, ret_string) \
+ /**/ o(string, ret_string) \
+ /**/
+ MUTATOR_HOOKABLE(SetWeaponreplace, EV_SetWeaponreplace);
+
+ /** called when an item is about to respawn */
+ #define EV_Item_RespawnCountdown(i, o) \
+ /**/ i(string, item_name) \
+ /**/ o(string, item_name) \
+ /**/ i(vector, item_color) \
+ /**/ o(vector, item_color) \
+ /**/
+ string item_name;
+ vector item_color;
+ MUTATOR_HOOKABLE(Item_RespawnCountdown, EV_Item_RespawnCountdown);
+
+ /** called when a bot checks a target to attack */
+ #define EV_BotShouldAttack(i, o) \
+ /**/ i(entity, checkentity) \
+ /**/
+ entity checkentity;
+ MUTATOR_HOOKABLE(BotShouldAttack, EV_BotShouldAttack);
+
+ /**
+ * called whenever a player goes through a portal gun teleport
+ * allows you to strip a player of an item if they go through the teleporter to help prevent cheating
+ */
+ #define EV_PortalTeleport(i, o) \
+ /**/ i(entity, self) \
+ /**/
+ MUTATOR_HOOKABLE(PortalTeleport, EV_PortalTeleport);
+
+ /**
+ * called whenever a player uses impulse 33 (help me) in cl_impulse.qc
+ * normally help me ping uses self.waypointsprite_attachedforcarrier,
+ * but if your mutator uses something different then you can handle it
+ * in a special manner using this hook
+ */
+ #define EV_HelpMePing(i, o) \
+ /** the player who pressed impulse 33 */ i(entity, self) \
+ /**/
+ MUTATOR_HOOKABLE(HelpMePing, EV_HelpMePing);
+
+ /**
+ * called when a vehicle initializes
+ * return true to remove the vehicle
+ */
+ MUTATOR_HOOKABLE(VehicleSpawn, EV_NO_ARGS);
+
+ /**
+ * called when a player enters a vehicle
+ * allows mutators to set special settings in this event
+ */
+ #define EV_VehicleEnter(i, o) \
+ /** player */ i(entity, vh_player) \
+ /** vehicle */ i(entity, vh_vehicle) \
+ /**/
+ entity vh_player;
+ entity vh_vehicle;
+ MUTATOR_HOOKABLE(VehicleEnter, EV_VehicleEnter);
+
+ /**
+ * called when a player touches a vehicle
+ * return true to stop player from entering the vehicle
+ */
+ #define EV_VehicleTouch(i, o) \
+ /** vehicle */ i(entity, self) \
+ /** player */ i(entity, other) \
+ /**/
+ MUTATOR_HOOKABLE(VehicleTouch, EV_VehicleTouch);
+
+ /**
+ * called when a player exits a vehicle
+ * allows mutators to set special settings in this event
+ */
+ #define EV_VehicleExit(i, o) \
+ /** player */ i(entity, vh_player) \
+ /** vehicle */ i(entity, vh_vehicle) \
+ /**/
+ MUTATOR_HOOKABLE(VehicleExit, EV_VehicleExit);
+
+ /** called when a speedrun is aborted and the player is teleported back to start position */
+ #define EV_AbortSpeedrun(i, o) \
+ /** player */ i(entity, self) \
+ /**/
+ MUTATOR_HOOKABLE(AbortSpeedrun, EV_AbortSpeedrun);
+
+ /** called at when a item is touched. Called early, can edit item properties. */
+ #define EV_ItemTouch(i, o) \
+ /** item */ i(entity, self) \
+ /** player */ i(entity, other) \
+ /**/
+ MUTATOR_HOOKABLE(ItemTouch, EV_ItemTouch);
+
+ enum {
+ MUT_ITEMTOUCH_CONTINUE, // return this flag to make the function continue as normal
+ MUT_ITEMTOUCH_RETURN, // return this flag to make the function return (handled entirely by mutator)
+ MUT_ITEMTOUCH_PICKUP // return this flag to have the item "picked up" and taken even after mutator handled it
+ };
+
+ /** called at when a player connect */
+ #define EV_ClientConnect(i, o) \
+ /** player */ i(entity, self) \
+ /**/
+ MUTATOR_HOOKABLE(ClientConnect, EV_ClientConnect);
+
+ #define EV_HavocBot_ChooseRole(i, o) \
+ /**/ i(entity, self) \
+ /**/
+ MUTATOR_HOOKABLE(HavocBot_ChooseRole, EV_HavocBot_ChooseRole);
+
+ /** called when a target is checked for accuracy */
+ #define EV_AccuracyTargetValid(i, o) \
+ /** attacker */ i(entity, frag_attacker) \
+ /** target */ i(entity, frag_target) \
+ /**/
+ MUTATOR_HOOKABLE(AccuracyTargetValid, EV_AccuracyTargetValid);
+ enum {
+ MUT_ACCADD_VALID, // return this flag to make the function continue if target is a client
+ MUT_ACCADD_INVALID, // return this flag to make the function always continue
+ MUT_ACCADD_INDIFFERENT // return this flag to make the function always return
+ };
+
+ /** Called when clearing the global parameters for a model */
+ MUTATOR_HOOKABLE(ClearModelParams, EV_NO_ARGS);
+
+ /** Called when getting the global parameters for a model */
+ #define EV_GetModelParams(i, o) \
+ /** entity id */ i(string, checkmodel_input) \
+ /** entity id */ i(string, checkmodel_command) \
+ /**/
+ string checkmodel_input, checkmodel_command;
+ MUTATOR_HOOKABLE(GetModelParams, EV_GetModelParams);
+
+ /** called when a bullet has hit a target */
+ #define EV_FireBullet_Hit(i, o) \
+ /**/ i(entity, self) \
+ /**/ i(entity, bullet_hit) \
+ /**/ i(vector, bullet_startpos) \
+ /**/ i(vector, bullet_endpos) \
+ /**/ i(float, frag_damage) \
+ /**/ o(float, frag_damage) \
+ /**/
+ entity bullet_hit;
+ //vector bullet_hitloc; // the end pos matches the hit location, apparently
+ vector bullet_startpos;
+ vector bullet_endpos;
+ //float frag_damage;
+ MUTATOR_HOOKABLE(FireBullet_Hit, EV_FireBullet_Hit);
+
+ #define EV_FixPlayermodel(i, o) \
+ /**/ i(string, ret_string) \
+ /**/ o(string, ret_string) \
+ /**/
+ MUTATOR_HOOKABLE(FixPlayermodel, EV_FixPlayermodel);
+
+ /** Return error to play frag remaining announcements */
+ MUTATOR_HOOKABLE(Scores_CountFragsRemaining, EV_NO_ARGS);
+ #endif