]> git.xonotic.org Git - xonotic/xonotic-data.pk3dir.git/commitdiff
Reintroduce the T-virus
authorMario <zacjardine@y7mail.com>
Tue, 11 Aug 2015 12:47:32 +0000 (22:47 +1000)
committerMario <zacjardine@y7mail.com>
Tue, 11 Aug 2015 12:47:32 +0000 (22:47 +1000)
21 files changed:
qcsrc/client/csqcmodel_hooks.qc
qcsrc/common/animdecide.qc
qcsrc/common/animdecide.qh
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/spawn.qc
qcsrc/common/monsters/sv_monsters.qc
qcsrc/common/monsters/sv_monsters.qh
qcsrc/server/autocvars.qh
qcsrc/server/command/cmd.qc
qcsrc/server/command/common.qc
qcsrc/server/command/common.qh
qcsrc/server/command/sv_cmd.qc
qcsrc/server/mutators/base.qh
qcsrc/server/mutators/gamemode_invasion.qc

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