From: Mario Date: Sat, 29 Aug 2015 11:54:45 +0000 (+1000) Subject: Merge branch 'master' into Mario/monsters_broken X-Git-Tag: xonotic-v0.8.2~1997^2~3 X-Git-Url: https://git.rm.cloudns.org/?a=commitdiff_plain;h=1556aa4ea70b3b275afb1cb4587e555fb44f71c3;p=xonotic%2Fxonotic-data.pk3dir.git Merge branch 'master' into Mario/monsters_broken # Conflicts: # qcsrc/common/monsters/all.inc # qcsrc/common/monsters/all.qc # qcsrc/common/monsters/all.qh # qcsrc/common/monsters/monster/mage.qc # qcsrc/common/monsters/monster/shambler.qc # qcsrc/common/monsters/monster/spider.qc # qcsrc/common/monsters/monster/wyvern.qc # qcsrc/common/monsters/monster/zombie.qc # qcsrc/common/monsters/sv_monsters.qc # qcsrc/server/command/cmd.qc # qcsrc/server/command/sv_cmd.qc # qcsrc/server/mutators/base.qh --- 1556aa4ea70b3b275afb1cb4587e555fb44f71c3 diff --cc qcsrc/common/animdecide.qc index bc8f96de5,0cba5d7f3..8a624aa2a --- a/qcsrc/common/animdecide.qc +++ b/qcsrc/common/animdecide.qc @@@ -10,12 -10,48 +10,65 @@@ #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 diff --cc qcsrc/common/monsters/all.inc index 67b510a7d,201b9a5a2..f44cabc5d --- a/qcsrc/common/monsters/all.inc +++ b/qcsrc/common/monsters/all.inc @@@ -1,10 -1,5 +1,10 @@@ +#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" - #include "monster/wyvern.qc" #include "monster/shambler.qc" + #include "monster/spider.qc" + #include "monster/wyvern.qc" + #include "monster/zombie.qc" diff --cc qcsrc/common/monsters/all.qh index 570254e92,4158b1537..0f2f13c5e --- a/qcsrc/common/monsters/all.qh +++ b/qcsrc/common/monsters/all.qh @@@ -8,12 -31,7 +31,9 @@@ const int MR_SETUP = 1; // (SERVER) set 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; @@@ -33,59 -47,9 +49,21 @@@ const int MON_FLAG_MELEE = 1024 .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 diff --cc qcsrc/common/monsters/monster/mage.qc index 9ca63bd96,e2258dbd4..d586321f6 --- a/qcsrc/common/monsters/monster/mage.qc +++ b/qcsrc/common/monsters/monster/mage.qc @@@ -1,18 -1,21 +1,23 @@@ - #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; @@@ -236,9 -236,9 +241,9 @@@ void M_Mage_Defend_Heal( } 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); } } @@@ -251,13 -250,13 +256,13 @@@ } } -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); } @@@ -338,11 -337,17 +343,11 @@@ float M_Mage_Attack(float attack_type 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) { @@@ -405,26 -389,38 +410,25 @@@ 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 diff --cc qcsrc/common/monsters/monster/shambler.qc index 4a48afef7,47e371a97..49bc3d8e6 --- a/qcsrc/common/monsters/monster/shambler.qc +++ b/qcsrc/common/monsters/monster/shambler.qc @@@ -1,23 -1,24 +1,28 @@@ - #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; @@@ -41,28 -40,26 +46,28 @@@ 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); + 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; } } @@@ -206,10 -197,14 +211,10 @@@ float M_Shambler_Attack(float attack_ty 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) { @@@ -252,17 -222,11 +257,18 @@@ { 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; } @@@ -276,5 -239,20 +282,3 @@@ 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 diff --cc qcsrc/common/monsters/monster/spider.qc index 550a563f7,de5f2c459..3c92f3b14 --- a/qcsrc/common/monsters/monster/spider.qc +++ b/qcsrc/common/monsters/monster/spider.qc @@@ -1,18 -1,21 +1,23 @@@ - #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; @@@ -92,7 -93,7 +97,7 @@@ void M_Spider_Attack_Web( 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) { @@@ -119,10 -119,14 +124,10 @@@ 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) { @@@ -159,25 -144,37 +164,24 @@@ 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 diff --cc qcsrc/common/monsters/monster/wyvern.qc index 28c98c129,849abc36c..bbe6c5d08 --- a/qcsrc/common/monsters/monster/wyvern.qc +++ b/qcsrc/common/monsters/monster/wyvern.qc @@@ -1,18 -1,21 +1,23 @@@ - #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; @@@ -96,11 -96,17 +101,10 @@@ float M_Wyvern_Attack(float attack_type 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) { @@@ -142,12 -126,10 +146,13 @@@ 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; } @@@ -161,5 -142,20 +166,3 @@@ 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 diff --cc qcsrc/common/monsters/monster/zombie.qc index c1bf45389,0f20daced..1ccfa82c8 --- a/qcsrc/common/monsters/monster/zombie.qc +++ b/qcsrc/common/monsters/monster/zombie.qc @@@ -1,18 -1,21 +1,23 @@@ - #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; @@@ -133,10 -130,14 +138,10 @@@ float M_Zombie_Attack(float attack_type 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) { @@@ -197,12 -163,11 +202,13 @@@ 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; } @@@ -216,5 -180,20 +222,3 @@@ 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 diff --cc qcsrc/common/monsters/sv_monsters.qc index b6e6fd1c8,020355077..c977aa67e --- a/qcsrc/common/monsters/sv_monsters.qc +++ b/qcsrc/common/monsters/sv_monsters.qc @@@ -23,14 -24,12 +24,13 @@@ #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() { @@@ -63,56 -61,71 +62,56 @@@ } } -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; @@@ -305,7 -258,7 +304,7 @@@ void Monster_Sounds_Clear( 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; @@@ -313,8 -266,8 +312,8 @@@ 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))) { @@@ -354,121 -311,45 +353,139 @@@ void Monster_Sound(.string samplefield 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; @@@ -485,10 -366,18 +502,17 @@@ } } - 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; } @@@ -888,19 -835,24 +907,19 @@@ void Monster_Move(float runspeed, floa 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); } @@@ -1108,65 -1053,49 +1126,102 @@@ void Monster_Damage(entity inflictor, e } } -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) @@@ -1176,9 -1105,8 +1231,11 @@@ 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(); } @@@ -1219,21 -1142,18 +1276,21 @@@ float Monster_Spawn_Setup( 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)) @@@ -1323,13 -1247,22 +1380,15 @@@ bool Monster_Spawn(int mon_id 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) { diff --cc qcsrc/server/command/common.qc index a1b0d9924,050aa5fac..ee832bcc8 --- a/qcsrc/server/command/common.qc +++ b/qcsrc/server/command/common.qc @@@ -312,147 -311,6 +311,147 @@@ void CommonCommand_cvar_purechanges(flo } } - 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) diff --cc qcsrc/server/mutators/events.qh index 000000000,33fa9ee3f..d84ec70eb mode 000000,100644..100644 --- a/qcsrc/server/mutators/events.qh +++ b/qcsrc/server/mutators/events.qh @@@ -1,0 -1,589 +1,596 @@@ + #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