]> de.git.xonotic.org Git - xonotic/xonotic-data.pk3dir.git/commitdiff
Begin cleaning up most monster functions (still a bit buggy)
authorMario <mario.mario@y7mail.com>
Wed, 28 Aug 2013 16:26:43 +0000 (02:26 +1000)
committerMario <mario.mario@y7mail.com>
Wed, 28 Aug 2013 16:26:43 +0000 (02:26 +1000)
47 files changed:
qcsrc/client/Main.qc
qcsrc/client/monsters.qc
qcsrc/client/monsters.qh
qcsrc/client/progs.src
qcsrc/common/monsters/all.qh [new file with mode: 0644]
qcsrc/common/monsters/lib/defs.qh [new file with mode: 0644]
qcsrc/common/monsters/lib/monsters.qc [new file with mode: 0644]
qcsrc/common/monsters/lib/monsters_early.qh [new file with mode: 0644]
qcsrc/common/monsters/lib/spawn.qc [new file with mode: 0644]
qcsrc/common/monsters/monster/animus.qc [new file with mode: 0644]
qcsrc/common/monsters/monster/bruiser.qc [new file with mode: 0644]
qcsrc/common/monsters/monster/brute.qc [new file with mode: 0644]
qcsrc/common/monsters/monster/cerberus.qc [new file with mode: 0644]
qcsrc/common/monsters/monster/knight.qc [new file with mode: 0644]
qcsrc/common/monsters/monster/mage.qc [new file with mode: 0644]
qcsrc/common/monsters/monster/shambler.qc [new file with mode: 0644]
qcsrc/common/monsters/monster/slime.qc [new file with mode: 0644]
qcsrc/common/monsters/monster/spider.qc [new file with mode: 0644]
qcsrc/common/monsters/monster/stingray.qc [new file with mode: 0644]
qcsrc/common/monsters/monster/wyvern.qc [new file with mode: 0644]
qcsrc/common/monsters/monster/zombie.qc [new file with mode: 0644]
qcsrc/common/monsters/monsters.qc [new file with mode: 0644]
qcsrc/common/monsters/monsters.qh [new file with mode: 0644]
qcsrc/server/command/cmd.qc
qcsrc/server/command/sv_cmd.qc
qcsrc/server/g_damage.qc
qcsrc/server/g_world.qc
qcsrc/server/monsters/lib/defs.qh [deleted file]
qcsrc/server/monsters/lib/monsters.qc [deleted file]
qcsrc/server/monsters/lib/monsters_early.qh [deleted file]
qcsrc/server/monsters/lib/spawn.qc [deleted file]
qcsrc/server/monsters/monster/animus.qc [deleted file]
qcsrc/server/monsters/monster/bruiser.qc [deleted file]
qcsrc/server/monsters/monster/brute.qc [deleted file]
qcsrc/server/monsters/monster/cerberus.qc [deleted file]
qcsrc/server/monsters/monster/knight.qc [deleted file]
qcsrc/server/monsters/monster/mage.qc [deleted file]
qcsrc/server/monsters/monster/shambler.qc [deleted file]
qcsrc/server/monsters/monster/slime.qc [deleted file]
qcsrc/server/monsters/monster/spider.qc [deleted file]
qcsrc/server/monsters/monster/stingray.qc [deleted file]
qcsrc/server/monsters/monster/wyvern.qc [deleted file]
qcsrc/server/monsters/monster/zombie.qc [deleted file]
qcsrc/server/monsters/monsters.qh [deleted file]
qcsrc/server/mutators/gamemode_invasion.qc
qcsrc/server/mutators/mutator_minstagib.qc
qcsrc/server/progs.src

index 6eef5cc0df53ddaa54e822b2332a179da52a9302..81d224faa89c5126a5b68e809a19006a0aea9769 100644 (file)
@@ -104,6 +104,7 @@ void CSQC_Init(void)
 
        // needs to be done so early because of the constants they create
        CALL_ACCUMULATED_FUNCTION(RegisterWeapons);
+       CALL_ACCUMULATED_FUNCTION(RegisterMonsters);
        CALL_ACCUMULATED_FUNCTION(RegisterGametypes);
        CALL_ACCUMULATED_FUNCTION(RegisterNotifications);
        CALL_ACCUMULATED_FUNCTION(RegisterDeathtypes);
@@ -115,8 +116,7 @@ void CSQC_Init(void)
        precache_model("null");
        precache_sound("misc/hit.wav");
        precache_sound("misc/typehit.wav");
-
-       Monsters_Precache();
+       
        generator_precache();
        Projectile_Precache();
        Hook_Precache();
index c3eef3c1666970e58083edd2c99de72128ac96d5..0dfbcf4c07b8c12f9369c83ac1527a6b94aee000 100644 (file)
@@ -1,207 +1,3 @@
-string mid2info_model;
-string mid2info_name;
-vector mid2info_min;
-vector mid2info_max;
-
-float monster_precached[MONSTER_LAST];
-void monster_mid2info(float _mid);
-
-void monster_precache(float _mid)
-{    
-    monster_mid2info(_mid);
-    if(monster_precached[_mid])
-        return;
-
-    switch(_mid)
-    {
-        case MONSTER_ZOMBIE:
-               {
-                       precache_model(ZOMBIE_MODEL);
-                       break;
-               }
-               case MONSTER_BRUTE:
-               {
-                       precache_model(BRUTE_MODEL);
-                       break;
-               }
-               case MONSTER_ANIMUS:
-               {
-                       precache_model(ANIMUS_MODEL);
-                       break;
-               }
-               case MONSTER_SHAMBLER:
-               {
-                       precache_model(SHAMBLER_MODEL);
-                       break;
-               }
-               case MONSTER_BRUISER:
-               {
-                       precache_model(BRUISER_MODEL);
-                       break;
-               }
-               case MONSTER_WYVERN:
-               {
-                       precache_model(WYVERN_MODEL);
-                       break;
-               }
-               case MONSTER_CERBERUS:
-               {
-                       precache_model(CERBERUS_MODEL);
-                       break;
-               }
-               case MONSTER_SLIME:
-               {
-                       precache_model(SLIME_MODEL);
-                       precache_sound("weapons/rocket_impact.wav");
-                       break;
-               }
-               case MONSTER_KNIGHT:
-               {
-                       precache_model(KNIGHT_MODEL);
-                       break;
-               }
-               case MONSTER_STINGRAY:
-               {
-                       precache_model(STINGRAY_MODEL);
-                       break;
-               }
-               case MONSTER_MAGE:
-               {
-                       precache_model(MAGE_MODEL);
-                       break;
-               }
-               case MONSTER_SPIDER:
-               {
-                       precache_model(SPIDER_MODEL);
-
-                       break;
-               }
-    }
-       
-    monster_precached[_mid] = TRUE;
-}
-
-void Monsters_Precache()
-{
-       float i;
-       for(i = MONSTER_FIRST + 1; i < MONSTER_LAST; ++i)
-               monster_precache(i);
-}
-
-void monster_mid2info(float _mid)
-{
-       switch(_mid)
-       {
-               case MONSTER_ZOMBIE:
-               {
-                       mid2info_model = ZOMBIE_MODEL;
-                       mid2info_name = "Zombie";
-                       mid2info_min = ZOMBIE_MIN;
-                       mid2info_max = ZOMBIE_MAX;
-                       break;
-               }
-               case MONSTER_BRUTE:
-               {
-                       mid2info_model = BRUTE_MODEL;
-                       mid2info_name = "Brute";
-                       mid2info_min = BRUTE_MIN;
-                       mid2info_max = BRUTE_MAX;
-                       break;
-               }
-               case MONSTER_ANIMUS:
-               {
-                       mid2info_model = ANIMUS_MODEL;
-                       mid2info_name = "Animus";
-                       mid2info_min = ANIMUS_MIN;
-                       mid2info_max = ANIMUS_MAX;
-                       if(self) self.scale = 1.3;
-                       break;
-               }
-               case MONSTER_SHAMBLER:
-               {
-                       mid2info_model = SHAMBLER_MODEL;
-                       mid2info_name = "Shambler";
-                       mid2info_min = SHAMBLER_MIN;
-                       mid2info_max = SHAMBLER_MAX;
-                       if(self) self.scale = 1.3;
-                       break;
-               }
-               case MONSTER_BRUISER:
-               {
-                       mid2info_model = BRUISER_MODEL;
-                       mid2info_name = "Bruiser";
-                       mid2info_min = BRUISER_MIN;
-                       mid2info_max = BRUISER_MAX;
-                       if(self) self.scale = 1.3;
-                       break;
-               }
-               case MONSTER_WYVERN:
-               {
-                       mid2info_model = WYVERN_MODEL;
-                       mid2info_name = "Wyvern";
-                       mid2info_min = WYVERN_MIN;
-                       mid2info_max = WYVERN_MAX;
-                       if(self) self.scale = 1.3;
-                       break;
-               }
-               case MONSTER_CERBERUS:
-               {
-                       mid2info_model = CERBERUS_MODEL;
-                       mid2info_name = "Cerberus";
-                       mid2info_min = CERBERUS_MIN;
-                       mid2info_max = CERBERUS_MAX;
-                       break;
-               }
-               case MONSTER_SLIME:
-               {
-                       mid2info_model = SLIME_MODEL;
-                       mid2info_name = "Slime";
-                       mid2info_min = SLIME_MIN;
-                       mid2info_max = SLIME_MAX;
-                       break;
-               }
-               case MONSTER_KNIGHT:
-               {
-                       mid2info_model = KNIGHT_MODEL;
-                       mid2info_name = "Knight";
-                       mid2info_min = KNIGHT_MIN;
-                       mid2info_max = KNIGHT_MAX;
-                       if(self) self.scale = 1.3;
-                       break;
-               }
-               case MONSTER_STINGRAY:
-               {
-                       mid2info_model = STINGRAY_MODEL;
-                       mid2info_name = "Stingray";
-                       mid2info_min = STINGRAY_MIN;
-                       mid2info_max = STINGRAY_MAX;
-                       if(self) self.scale = 1.3;
-                       break;
-               }
-               case MONSTER_MAGE:
-               {
-                       mid2info_model = MAGE_MODEL;
-                       mid2info_name = "Mage";
-                       mid2info_min = MAGE_MIN;
-                       mid2info_max = MAGE_MAX;
-                       break;
-               }
-               case MONSTER_SPIDER:
-               {
-                       mid2info_model = SPIDER_MODEL;
-                       mid2info_name = "Spider";
-                       mid2info_min = SPIDER_MIN;
-                       mid2info_max = SPIDER_MAX;
-                       break;
-               }
-               default:
-               {
-                       dprint("WARNING: Unknown monster in CSQC\n");
-                       break;
-               }
-       }       
-}
-
 .vector glowmod;
 void monster_changeteam()
 {
@@ -216,8 +12,7 @@ void monster_changeteam()
 
 void monster_die()
 {
-       if(self.monsterid == MONSTER_SPIDER)
-               self.angles += '180 0 0';
+       MON_ACTION(self.monsterid, MR_DEATH);
                
        self.solid = SOLID_CORPSE;
 }
@@ -238,13 +33,21 @@ void monster_draw()
 }
 
 void monster_construct()
-{      
-       monster_mid2info(self.monsterid);
-       self.netname = mid2info_name;
+{
+       vector min_s, max_s;
+       entity mon = get_monsterinfo(self.monsterid);
+       
+       min_s = mon.mins;
+       max_s = mon.maxs;
+       
+       if(mon.spawnflags & MONSTER_SIZE_BROKEN)
+               self.scale = 1.3;
+       
+       self.netname = M_NAME(self.monsterid);
 
        setorigin(self, self.origin);
-       setmodel(self, mid2info_model);
-       setsize(self, mid2info_min, mid2info_max);
+       setmodel(self, mon.model);
+       setsize(self, min_s, max_s);
        
        self.move_movetype      = MOVETYPE_BOUNCE;
        self.health                     = 255;
@@ -277,7 +80,6 @@ void ent_monster()
                self.skin = ReadByte();
                self.team = ReadByte();
                
-               monster_precache(self.monsterid);
                monster_construct();
                monster_changeteam();
        }
index 83dac2956fa224faa866c9aed2f06dee99234186..aa6fb41260b5e5e47f5b99e73edb9f743634ed27 100644 (file)
@@ -1,2 +1 @@
 void ent_monster();
-void Monsters_Precache();
index fd700325050b11a314e9bde77a13b03461c65686..bc1ec35eba339a8dbf3006ad67ef20ca7e13490e 100644 (file)
@@ -27,6 +27,7 @@ Defs.qc
 ../common/command/shared_defs.qh
 ../common/urllib.qh
 ../common/animdecide.qh
+../common/monsters/monsters.qh
 command/cl_cmd.qh
 
 autocvars.qh
@@ -48,7 +49,7 @@ noise.qh
 tturrets.qh
 ../server/tturrets/include/turrets_early.qh
 monsters.qh
-../server/monsters/lib/monsters_early.qh
+../common/monsters/lib/monsters_early.qh
 ../server/movelib.qc
 ../server/generator.qh
 main.qh
@@ -114,6 +115,7 @@ noise.qc
 ../server/w_all.qc
 ../common/explosion_equation.qc
 ../common/urllib.qc
+../common/monsters/monsters.qc
 command/cl_cmd.qc
 
 ../warpzonelib/anglestransform.qc
@@ -124,7 +126,6 @@ tturrets.qc
 
 ../server/generator.qc
 
-../server/monsters/monsters.qh
 monsters.qc
 
 player_skeleton.qc
diff --git a/qcsrc/common/monsters/all.qh b/qcsrc/common/monsters/all.qh
new file mode 100644 (file)
index 0000000..0c4f0af
--- /dev/null
@@ -0,0 +1,12 @@
+#include "monster/brute.qc"
+#include "monster/animus.qc"
+#include "monster/shambler.qc"
+#include "monster/bruiser.qc"
+#include "monster/wyvern.qc"
+#include "monster/cerberus.qc"
+#include "monster/slime.qc"
+#include "monster/knight.qc"
+#include "monster/stingray.qc"
+#include "monster/mage.qc"
+#include "monster/zombie.qc"
+#include "monster/spider.qc"
diff --git a/qcsrc/common/monsters/lib/defs.qh b/qcsrc/common/monsters/lib/defs.qh
new file mode 100644 (file)
index 0000000..1f5fe12
--- /dev/null
@@ -0,0 +1,51 @@
+.float(float attack_type) monster_attackfunc;
+const float MONSTER_ATTACK_MELEE       = 1;
+const float MONSTER_ATTACK_RANGED      = 2;
+
+.float candrop;
+
+.float attack_range;
+
+.float spawn_time; // stop monster from moving around right after spawning
+
+.string oldtarget2;
+.float lastshielded;
+
+.vector oldangles;
+
+.float monster_respawned; // used to make sure we're not recounting respawned monster stats
+
+float monsters_spawned;
+
+const float MONSTERSKILL_NOTEASY = 256; // monster will not spawn on skill <= 2
+const float MONSTERSKILL_NOTMEDIUM = 512; // monster will not spawn on skill 3
+const float MONSTERSKILL_NOTHARD = 1024; // monster will not spawn on skill 4
+const float MONSTERSKILL_NOTINSANE = 2048; // monster will not spawn on skill 5
+const float MONSTERSKILL_NOTNIGHTMARE = 4096; // monster will not spawn on skill >= 6
+
+// new flags
+const float MONSTERFLAG_MINIBOSS = 1;  // monster spawns as mini-boss (also has a chance of naturally becoming one)
+const float MONSTERFLAG_APPEAR = 2; // delay spawn until triggered
+const float MONSTERFLAG_NORESPAWN = 4;
+const float MONSTERFLAG_SPAWNED = 512; // flag for spawned monsters
+
+.float msound_delay; // restricts some monster sounds
+.string msound_idle;
+.string msound_death;
+.string msound_attack_melee;
+.string msound_attack_ranged;
+.string msound_spawn;
+.string msound_sight;
+.string msound_pain;
+
+.void() monster_spawnfunc;
+
+.float monster_movestate; // used to tell what the monster is currently doing
+const float MONSTER_MOVE_OWNER = 1; // monster will move to owner if in range, or stand still
+const float MONSTER_MOVE_WANDER = 2; // monster will ignore owner & wander around
+const float MONSTER_MOVE_SPAWNLOC = 3; // monster will move to its spawn location when not attacking
+const float MONSTER_MOVE_NOMOVE = 4; // monster simply stands still
+const float MONSTER_MOVE_ENEMY = 5; // used only as a movestate
+
+const float MONSTER_STATE_ATTACK_LEAP = 1;
+const float MONSTER_STATE_ATTACK_MELEE = 2;
diff --git a/qcsrc/common/monsters/lib/monsters.qc b/qcsrc/common/monsters/lib/monsters.qc
new file mode 100644 (file)
index 0000000..4850dc1
--- /dev/null
@@ -0,0 +1,1096 @@
+// TODO: clean up this file?
+
+void() spawnfunc_item_minst_cells;
+
+void M_Item_Touch ()
+{
+       if(self && IS_PLAYER(other) && other.deadflag == DEAD_NO)
+       {
+               Item_Touch();
+               self.think = SUB_Remove;
+               self.nextthink = time + 0.1;
+       }
+}
+
+void monster_item_spawn()
+{
+       if(self.monster_loot)
+               self.monster_loot();
+       
+       self.gravity = 1;
+       self.velocity = randomvec() * 175 + '0 0 325';
+       self.touch = M_Item_Touch;
+       
+       SUB_SetFade(self, time + autocvar_g_monsters_drop_time, 1);
+}
+
+void Monster_DropItem (string itype, string itemsize)
+{
+       vector org = self.origin + ((self.mins + self.maxs) * 0.5);
+       entity e = spawn();
+       
+       setorigin(e, org);
+       
+       switch(itype)
+       {
+               case "armor":
+               {
+                       switch(itemsize)
+                       {
+                               case "mega": e.monster_loot = spawnfunc_item_armor_large; break;
+                               case "large": e.monster_loot = spawnfunc_item_armor_big; break;
+                               case "medium": e.monster_loot = spawnfunc_item_armor_medium; break;
+                               case "small": e.monster_loot = spawnfunc_item_armor_small; break;
+                       }
+                       break;
+               }
+               case "health":
+               {
+                       switch(itemsize)
+                       {
+                               case "mega": e.monster_loot = spawnfunc_item_health_mega; break;
+                               case "large": e.monster_loot = spawnfunc_item_health_large; break;
+                               case "medium": e.monster_loot = spawnfunc_item_health_medium; break;
+                               case "small": e.monster_loot = spawnfunc_item_health_small; break;
+                       }
+                       break;
+               }
+               case "ammo":
+               {
+                       switch(itemsize)
+                       {
+                               case "shells": e.monster_loot = spawnfunc_item_shells; break;
+                               case "cells": e.monster_loot = spawnfunc_item_cells; break;
+                               case "rockets": e.monster_loot = spawnfunc_item_rockets; break;
+                               case "bullets":
+                               case "nails": e.monster_loot = spawnfunc_item_bullets; break;
+                       }
+                       break;
+               }
+       }
+       
+       other = e;
+       MUTATOR_CALLHOOK(MonsterDropItem);
+       e = other;
+               
+       e.think = monster_item_spawn;
+       e.nextthink = time + 0.1;
+}
+
+void monsters_setframe(float _frame)
+{
+       if(self.frame == _frame)
+               return;
+               
+       self.anim_start_time = time;
+       self.frame = _frame;
+       self.SendFlags |= MSF_ANIM;
+}
+
+float monster_isvalidtarget (entity targ, entity ent)
+{
+       if(!targ || !ent)
+               return FALSE; // someone doesn't exist
+               
+       if(time < game_starttime)
+               return FALSE; // monsters do nothing before the match has started
+               
+       WarpZone_TraceLine(ent.origin, targ.origin, MOVE_NORMAL, ent);
+       
+       if(vlen(targ.origin - ent.origin) >= ent.target_range)
+               return FALSE; // enemy is too far away
+               
+       if not(targ.vehicle_flags & VHF_ISVEHICLE)
+       if(trace_ent != targ)
+               return FALSE; // we can't see the enemy
+               
+       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 not(targ.vehicle_flags & VHF_ISVEHICLE) // vehicles dont count as alive?
+       if(targ.deadflag != DEAD_NO || ent.deadflag != DEAD_NO || targ.health <= 0 || ent.health <= 0)
+               return FALSE; // enemy/self is dead
+       
+       if(targ.monster_owner == ent || ent.monster_owner == targ)
+               return FALSE; // enemy owns us, or we own them
+       
+       if not(targ.vehicle_flags & VHF_ISVEHICLE)
+       if(targ.flags & FL_NOTARGET)
+               return FALSE; // enemy can't be targeted
+       
+       if not(autocvar_g_monsters_typefrag)
+       if(targ.BUTTON_CHAT)
+               return FALSE; // no typefragging!
+       
+       if not(IsDifferentTeam(targ, ent))
+               return FALSE; // enemy is on our team
+               
+       if(autocvar_g_monsters_target_infront)
+       if(ent.enemy != targ)
+       {
+               float dot;
+
+               makevectors (ent.angles);
+               dot = normalize (targ.origin - ent.origin) * v_forward;
+               
+               if(dot <= 0.3)
+                       return FALSE;
+       }
+       
+       return TRUE;
+}
+
+entity FindTarget (entity ent) 
+{
+       if(MUTATOR_CALLHOOK(MonsterFindTarget)) { return ent.enemy; } // Handled by a mutator
+       entity e;
+       
+       for(e = world; (e = findflags(e, monster_attack, TRUE)); ) 
+       if(monster_isvalidtarget(e, ent))
+               return e;
+
+       return world;
+}
+
+void MonsterTouch ()
+{
+       if(other == world)
+               return;
+               
+       if(self.enemy != other)
+       if not(other.flags & FL_MONSTER)
+       if(monster_isvalidtarget(other, self))
+               self.enemy = other;
+}
+
+void monster_sound(string msound, float sound_delay, float delaytoo)
+{
+       if(delaytoo && time < self.msound_delay)
+               return; // too early
+               
+       if(msound == "")
+               return; // sound doesn't exist
+
+       sound(self, CHAN_AUTO, msound, VOL_BASE, ATTN_NORM);
+
+       self.msound_delay = time + sound_delay;
+}
+
+void monster_precachesounds(entity e)
+{
+       precache_sound(e.msound_idle);
+       precache_sound(e.msound_death);
+       precache_sound(e.msound_attack_melee);
+       precache_sound(e.msound_attack_ranged);
+       precache_sound(e.msound_sight);
+       precache_sound(e.msound_pain);
+}
+
+void monster_setupsounds(string mon)
+{
+       if(self.msound_idle == "") self.msound_idle = strzone(strcat("monsters/", mon, "_idle.wav"));
+       if(self.msound_death == "") self.msound_death = strzone(strcat("monsters/", mon, "_death.wav"));
+       if(self.msound_pain == "") self.msound_pain = strzone(strcat("monsters/", mon, "_pain.wav"));
+       if(self.msound_attack_melee == "") self.msound_attack_melee = strzone(strcat("monsters/", mon, "_melee.wav"));
+       if(self.msound_attack_ranged == "") self.msound_attack_ranged = strzone(strcat("monsters/", mon, "_attack.wav"));
+       if(self.msound_sight == "") self.msound_sight = strzone(strcat("monsters/", mon, "_sight.wav"));
+}
+
+float monster_melee (entity targ, float damg, float er, float deathtype, float dostop)
+{
+       float dot, rdmg = damg * random();
+
+       if (self.health <= 0)
+               return FALSE;
+       if (targ == world)
+               return FALSE;
+               
+       if(dostop)
+       {
+               self.velocity_x = 0;
+               self.velocity_y = 0;
+               self.state = MONSTER_STATE_ATTACK_MELEE;
+               self.SendFlags |= MSF_MOVE;
+       }
+
+       makevectors (self.angles);
+       dot = normalize (targ.origin - self.origin) * v_forward;
+       
+       if(dot > er)
+               Damage(targ, self, self, rdmg * monster_skill, deathtype, targ.origin, normalize(targ.origin - self.origin));
+               
+       return TRUE;
+}
+
+void Monster_CheckDropCvars (string mon)
+{
+       if not(self.candrop)
+               return; // forced off
+       
+       string dropitem;
+       string dropsize;
+       
+       dropitem = cvar_string(strcat("g_monster_", mon, "_drop"));
+       dropsize = cvar_string(strcat("g_monster_", mon, "_drop_size"));
+       
+       if(autocvar_g_monsters_forcedrop)
+               Monster_DropItem(autocvar_g_monsters_drop_type, autocvar_g_monsters_drop_size);
+       else if(dropitem != "")
+               Monster_DropItem(dropitem, dropsize);      
+       else
+               Monster_DropItem("armor", "medium");
+}
+
+void Monster_CheckMinibossFlag ()
+{
+       if(MUTATOR_CALLHOOK(MonsterCheckBossFlag))
+               return;
+               
+       float chance = random() * 100;
+
+       // g_monsters_miniboss_chance cvar or spawnflags 64 causes a monster to be a miniboss
+       if ((self.spawnflags & MONSTERFLAG_MINIBOSS) || (chance < autocvar_g_monsters_miniboss_chance))
+       {
+               self.health += autocvar_g_monsters_miniboss_healthboost;
+               self.flags |= MONSTERFLAG_MINIBOSS;
+               if not(self.weapon)
+                       self.weapon = WEP_NEX;
+       }
+}
+
+float Monster_CanRespawn(entity ent)
+{
+       other = ent;
+       if(MUTATOR_CALLHOOK(MonsterRespawn))
+               return TRUE; // enabled by a mutator
+               
+       if(ent.spawnflags & MONSTERFLAG_NORESPAWN)
+               return FALSE;
+               
+       if not(autocvar_g_monsters_respawn)
+               return FALSE;
+               
+       return TRUE;
+}
+
+void Monster_Fade ()
+{
+       if(Monster_CanRespawn(self))
+       {
+               self.monster_respawned = TRUE;
+               self.think = self.monster_spawnfunc;
+               self.nextthink = time + self.respawntime;
+               self.deadflag = DEAD_RESPAWNING;
+               if(self.spawnflags & MONSTER_RESPAWN_DEATHPOINT)
+               {
+                       self.pos1 = self.origin;
+                       self.pos2 = self.angles;
+               }
+               self.event_damage = func_null;
+               self.takedamage = DAMAGE_NO;
+               setorigin(self, self.pos1);
+               self.angles = self.pos2;
+               self.health = self.max_health; // TODO: check if resetting to max_health is wise here
+               
+               self.SendFlags |= MSF_MOVE;
+               self.SendFlags |= MSF_STATUS;
+               
+               return;
+       }
+       SUB_SetFade(self, time + 3, 1);
+}
+
+float Monster_CanJump (vector vel)
+{
+       if(self.state)
+               return FALSE; // already attacking
+       if not(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;
+               
+       monsters_setframe(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 not(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))
+       {
+               monster_sound(e.msound_attack_melee, 0, FALSE);
+               return;
+       }
+       
+       if(e.monster_attackfunc(MONSTER_ATTACK_RANGED))
+       {
+               monster_sound(e.msound_attack_ranged, 0, FALSE);
+               return;
+       }
+}
+
+void monster_makevectors(entity e)
+{
+       vector v;
+               
+       v = CENTER_OR_VIEWOFS(e);
+       self.v_angle = vectoangles(v - (self.origin + self.view_ofs));
+       self.v_angle_x = -self.v_angle_x;
+       
+       makevectors(self.v_angle);
+}
+
+void monster_use ()
+{
+       if (self.enemy)
+               return;
+       if (self.health <= 0)
+               return;
+
+       if(!monster_isvalidtarget(activator, self))
+               return;
+
+       self.enemy = activator;
+}
+
+float trace_path(vector from, vector to)
+{
+       vector dir = normalize(to - from) * 15, offset = '0 0 0';
+       float trace1 = trace_fraction;
+       
+       offset_x = dir_y;
+       offset_y = -dir_x;
+       traceline (from+offset, to+offset, TRUE, self);
+       
+       traceline(from-offset, to-offset, TRUE, self);
+               
+       return ((trace1 < trace_fraction) ? trace1 : trace_fraction);
+}
+
+.float last_trace;
+.float last_enemycheck; // for checking enemy
+vector monster_pickmovetarget(entity targ)
+{
+       // enemy is always preferred target
+       if(self.enemy)
+       {
+               self.monster_movestate = MONSTER_MOVE_ENEMY;
+               self.last_trace = time + 1.2;
+               return self.enemy.origin;
+       }
+       
+       switch(self.monster_moveflags)
+       {
+               case MONSTER_MOVE_OWNER:
+               {
+                       self.monster_movestate = MONSTER_MOVE_OWNER;
+                       self.last_trace = time + 0.3;
+                       if(self.monster_owner && self.monster_owner.classname != "td_spawnpoint")
+                               return self.monster_owner.origin;
+               }
+               case MONSTER_MOVE_SPAWNLOC:
+               {
+                       self.monster_movestate = MONSTER_MOVE_SPAWNLOC;
+                       self.last_trace = time + 2;
+                       return self.pos1;
+               }
+               case MONSTER_MOVE_NOMOVE:
+               {
+                       self.monster_movestate = MONSTER_MOVE_NOMOVE;
+                       self.last_trace = time + 2;
+                       return self.origin;
+               }
+               default:
+               case MONSTER_MOVE_WANDER:
+               {
+                       vector pos;
+                       self.monster_movestate = MONSTER_MOVE_WANDER;
+                       self.last_trace = time + 2;
+                               
+                       self.angles_y = random() * 500;
+                       makevectors(self.angles);
+                       pos = self.origin + v_forward * 600;
+                       
+                       if(self.flags & FL_FLY || self.flags & FL_SWIM)
+                       {
+                               pos_z = random() * 200;
+                               if(random() >= 0.5)
+                                       pos_z *= -1;
+                       }
+                       
+                       if(targ)
+                       {
+                               self.last_trace = time + 0.5;
+                               pos = targ.origin;
+                       }
+                       
+                       return pos;
+               }
+       }
+}
+
+void monster_move(float runspeed, float walkspeed, float stopspeed, float manim_run, float manim_walk, float manim_idle)
+{
+       fixedmakevectors(self.angles);
+
+       if(self.target2)
+               self.goalentity = find(world, targetname, self.target2);
+               
+       entity targ;
+
+       if(self.frozen)
+       {
+               self.revive_progress = bound(0, self.revive_progress + frametime * self.revive_speed, 1);
+               self.health = max(1, self.max_health * self.revive_progress);
+               
+               if(self.sprite) WaypointSprite_UpdateHealth(self.sprite, self.health);
+                       
+               movelib_beak_simple(stopspeed);
+                       
+               self.velocity = '0 0 0';
+               self.enemy = world;
+               self.nextthink = time + 0.1;
+               
+               if(self.revive_progress >= 1)
+                       Unfreeze(self); // wait for next think before attacking
+                       
+               // don't bother updating angles here?
+               if(self.origin != self.oldorigin)
+               {
+                       self.oldorigin = self.origin;
+                       self.SendFlags |= MSF_MOVE;
+               }
+                       
+               return; // no moving while frozen
+       }
+       
+       if(self.flags & FL_SWIM)
+       {
+               if(self.waterlevel < WATERLEVEL_WETFEET)
+               {
+                       if(time >= self.last_trace)
+                       {
+                               self.last_trace = time + 0.4;
+                               
+                               Damage (self, world, world, 2, DEATH_DROWN, self.origin, '0 0 0');
+                               self.angles = '90 90 0';
+                               if(random() < 0.5)
+                               {
+                                       self.velocity_y += random() * 50;
+                                       self.velocity_x -= random() * 50;
+                               }
+                               else
+                               {
+                                       self.velocity_y -= random() * 50;
+                                       self.velocity_x += random() * 50;
+                               }
+                               self.velocity_z += random() * 150;
+                       }
+                               
+                       
+                       self.movetype = MOVETYPE_BOUNCE;
+                       //self.velocity_z = -200;
+                               
+                       self.SendFlags |= MSF_MOVE | MSF_ANG;
+                       
+                       return;
+               }
+               else
+               {
+                       self.angles = '0 0 0';
+                       self.movetype = MOVETYPE_WALK;
+               }
+       }
+       
+       targ = self.goalentity;
+       
+       monster_target = targ;
+       monster_speed_run = runspeed;
+       monster_speed_walk = walkspeed;
+       
+       if(MUTATOR_CALLHOOK(MonsterMove) || gameover || (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)
+                       monsters_setframe(manim_idle);
+               movelib_beak_simple(stopspeed);
+               self.SendFlags |= MSF_MOVE;
+               return;
+       }
+       
+       targ = monster_target;
+       runspeed = monster_speed_run;
+       walkspeed = monster_speed_walk;
+               
+       if(IsDifferentTeam(self.monster_owner, self))
+               self.monster_owner = world;
+               
+       if(time >= self.last_enemycheck)
+       {
+               if not(monster_isvalidtarget(self.enemy, self))
+                       self.enemy = world;
+               self.last_enemycheck = time + 2;
+       }
+               
+       if(self.enemy && self.enemy.health < 1)
+               self.enemy = world; // enough!
+               
+       if not(self.enemy)
+       {
+               self.enemy = FindTarget(self);
+               if(self.enemy)
+                       monster_sound(self.msound_sight, 0, FALSE);
+       }
+       
+       if(self.state == MONSTER_STATE_ATTACK_MELEE && time >= self.attack_finished_single)
+               self.state = 0;
+               
+       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 not(self.enemy)
+               monster_sound(self.msound_idle, 5, TRUE);
+       
+       if(self.state != MONSTER_STATE_ATTACK_LEAP && self.state != MONSTER_STATE_ATTACK_MELEE)
+               self.steerto = steerlib_attract2(self.moveto, 0.5, 500, 0.95);
+       
+       if(self.state == MONSTER_STATE_ATTACK_LEAP && (self.flags & FL_ONGROUND))
+       {
+               self.state = 0;
+               self.touch = MonsterTouch;
+       }
+       
+       //self.steerto = steerlib_attract2(self.moveto, 0.5, 500, 0.95);
+       
+       float turny = 0;
+       vector real_angle = vectoangles(self.steerto) - self.angles;
+       
+       if(self.state != MONSTER_STATE_ATTACK_LEAP && self.state != MONSTER_STATE_ATTACK_MELEE)
+               turny = 20;
+               
+       if(self.flags & FL_SWIM)
+               turny = vlen(self.angles - self.moveto);
+       
+       if(turny)
+       {
+               turny = bound(turny * -1, shortangle_f(real_angle_y, self.angles_y), turny);
+               self.angles_y += turny;
+       }
+       
+       if(self.state == MONSTER_STATE_ATTACK_MELEE)
+               self.moveto = self.origin;
+       else if(self.enemy)
+               self.moveto = self.moveto * 0.9 + ((self.origin + v_forward * 500) + randomvec() * 400) * 0.1;
+       
+       if not(self.flags & FL_FLY || self.flags & FL_SWIM)
+               self.moveto_z = self.origin_z; 
+       
+       float l = vlen(self.moveto - self.origin);
+       float t1 = trace_path(self.origin+'0 0 10', self.moveto+'0 0 10');
+       float t2 = trace_path(self.origin-'0 0 15', self.moveto-'0 0 15'); 
+       
+       if(self.flags & FL_FLY || self.flags & FL_SWIM)
+               v_forward = normalize(self.moveto - self.origin);
+       
+       if(t1*l-t2*l>50 && (t1*l > 100 || t1 > 0.8))
+       if(self.flags & FL_ONGROUND)
+               movelib_jump_simple(100);
+
+       if(vlen(self.origin - self.moveto) > 64)
+       {
+               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);
+               if(time > self.pain_finished)
+               if(time > self.attack_finished_single)
+                       monsters_setframe((self.enemy) ? manim_run : manim_walk);
+       }
+       else
+       {
+               entity e = find(world, targetname, self.target2);
+               if(e.target2)
+                       self.target2 = e.target2;
+               else if(e.target)
+                       self.target2 = e.target;
+               
+               movelib_beak_simple(stopspeed);
+               if(time > self.attack_finished_single)
+               if(time > self.pain_finished)
+               if (vlen(self.velocity) <= 30)
+                       monsters_setframe(manim_idle);
+       }
+       
+       monster_checkattack(self, self.enemy);
+       
+       if(self.angles != self.oldangles)
+       {
+               self.oldangles = self.angles;
+               self.SendFlags |= MSF_ANG;
+       }
+       
+       if(self.origin != self.oldorigin)
+       {
+               self.oldorigin = self.origin;
+               self.SendFlags |= MSF_MOVE;
+       }
+}
+
+void monster_dead_think()
+{
+       self.think = monster_dead_think;
+       self.nextthink = time + 0.3; // don't need to update so often now
+       
+       self.deadflag = DEAD_DEAD;
+
+       if(time >= self.ltime)
+       {
+               Monster_Fade();
+               return;
+       }
+       
+       self.SendFlags |= MSF_MOVE; // keep up to date on the monster's location
+}
+
+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;
+       self.monster_spawnfunc();
+}
+
+float Monster_CheckAppearFlags(entity ent)
+{
+       if not(ent.spawnflags & MONSTERFLAG_APPEAR)
+               return FALSE;
+       
+       ent.think = func_null;
+       ent.nextthink = 0;
+       ent.use = Monster_Appear;
+       ent.flags = FL_MONSTER; // set so this monster can get butchered
+       
+       return TRUE;
+}
+
+void monsters_reset()
+{
+       setorigin(self, self.pos1);
+       self.angles = self.pos2;
+       
+       self.health = self.max_health;
+       self.velocity = '0 0 0';
+       self.enemy = world;
+       self.goalentity = world;
+       self.attack_finished_single = 0;
+       self.moveto = self.origin;
+       
+       WaypointSprite_UpdateHealth(self.sprite, self.health);
+}
+
+float monster_send(entity to, float sf)
+{
+       WriteByte(MSG_ENTITY, ENT_CLIENT_MONSTER);    
+       WriteByte(MSG_ENTITY, sf);
+       if(sf & MSF_SETUP)
+       {
+           WriteByte(MSG_ENTITY, self.monsterid);
+           
+           WriteCoord(MSG_ENTITY, self.origin_x);
+           WriteCoord(MSG_ENTITY, self.origin_y);
+           WriteCoord(MSG_ENTITY, self.origin_z);
+           
+           WriteAngle(MSG_ENTITY, self.angles_x);
+           WriteAngle(MSG_ENTITY, self.angles_y);
+               
+               WriteByte(MSG_ENTITY, self.skin);
+               WriteByte(MSG_ENTITY, self.team);
+    }
+    
+    if(sf & MSF_ANG)
+    {
+        WriteShort(MSG_ENTITY, rint(self.angles_x));
+        WriteShort(MSG_ENTITY, rint(self.angles_y));
+    }
+    
+    if(sf & MSF_MOVE)
+    {
+        WriteShort(MSG_ENTITY, rint(self.origin_x));
+        WriteShort(MSG_ENTITY, rint(self.origin_y));
+        WriteShort(MSG_ENTITY, rint(self.origin_z));
+
+        WriteShort(MSG_ENTITY, rint(self.velocity_x));
+        WriteShort(MSG_ENTITY, rint(self.velocity_y));
+        WriteShort(MSG_ENTITY, rint(self.velocity_z));        
+        
+        WriteShort(MSG_ENTITY, rint(self.angles_y));        
+    }
+    
+    if(sf & MSF_ANIM)
+    {
+        WriteCoord(MSG_ENTITY, self.anim_start_time);
+        WriteByte(MSG_ENTITY, self.frame);
+    }
+    
+    if(sf & MSF_STATUS)
+    {
+               WriteByte(MSG_ENTITY, self.skin);
+               
+        WriteByte(MSG_ENTITY, self.team);
+               
+               WriteByte(MSG_ENTITY, self.deadflag);
+        
+        if(self.health <= 0)
+            WriteByte(MSG_ENTITY, 0);
+        else
+            WriteByte(MSG_ENTITY, ceil((self.health / self.max_health) * 255));
+    }
+    
+       return TRUE;
+}
+
+void monster_link(void() spawnproc)
+{
+    Net_LinkEntity(self, TRUE, 0, monster_send);
+    self.think      = spawnproc;
+    self.nextthink  = time;
+}
+
+void monsters_corpse_damage (entity inflictor, entity attacker, float damage, float deathtype, vector hitloc, vector force)
+{
+       self.health -= damage;
+               
+       Violence_GibSplash_At(hitloc, force, 2, bound(0, damage, 200) / 16, self, attacker);
+               
+       if(self.health <= -100) // 100 health until gone?
+       {
+               Violence_GibSplash_At(hitloc, force, 2, bound(0, damage, 200) / 16, self, attacker);
+               
+               self.think = SUB_Remove;
+               self.nextthink = time + 0.1;
+       }
+}
+
+void monster_die()
+{
+       self.think = monster_dead_think;
+       self.nextthink = self.ticrate;
+       self.ltime = time + 5;
+       
+       Monster_CheckDropCvars(self.netname);
+
+       WaypointSprite_Kill(self.sprite);
+               
+       if(self.weaponentity)
+       {
+               remove(self.weaponentity);
+               self.weaponentity = world;
+       }
+               
+       monster_sound(self.msound_death, 0, FALSE);
+               
+       if(!(self.spawnflags & MONSTERFLAG_SPAWNED) && !self.monster_respawned)
+               monsters_killed += 1;
+               
+       if(self.candrop && self.weapon)
+               W_ThrowNewWeapon(self, self.weapon, 0, self.origin, randomvec() * 150 + '0 0 325');     
+               
+       if(IS_CLIENT(self.realowner))
+               self.realowner.monstercount -= 1;
+               
+       self.event_damage       = monsters_corpse_damage;
+       self.solid                      = SOLID_CORPSE;
+       self.takedamage         = DAMAGE_AIM;
+       self.enemy                      = world;
+       self.movetype           = MOVETYPE_TOSS;
+       self.moveto                     = self.origin;
+       self.touch                      = MonsterTouch; // reset incase monster was pouncing
+       
+       if not(self.flags & FL_FLY)
+               self.velocity = '0 0 0';
+       
+       self.SendFlags |= MSF_MOVE;
+               
+       totalspawned -= 1;
+       
+       MON_ACTION(self.monsterid, MR_DEATH);
+}
+
+void monsters_damage (entity inflictor, entity attacker, float damage, float deathtype, vector hitloc, vector force)
+{
+       if(self.frozen && deathtype != DEATH_KILL)
+               return;
+               
+       if(time < self.pain_finished && deathtype != DEATH_KILL)
+               return;
+               
+       if(time < self.spawnshieldtime)
+               return;
+               
+       if(deathtype != DEATH_KILL)
+               damage *= self.armorvalue;
+               
+       if(self.weaponentity && self.weaponentity.classname == "shield")
+               self.weaponentity.health -= damage;
+               
+       self.health -= damage;
+       
+       if(self.sprite)
+               WaypointSprite_UpdateHealth(self.sprite, self.health);
+               
+       self.dmg_time = time;
+
+       if(sound_allowed(MSG_BROADCAST, attacker) && deathtype != DEATH_DROWN)
+               spamsound (self, CH_PAIN, "misc/bodyimpact1.wav", VOL_BASE, ATTN_NORM);  // FIXME: PLACEHOLDER
+       
+       self.velocity += force * self.damageforcescale;
+               
+       if(deathtype != DEATH_DROWN)
+       {
+               Violence_GibSplash_At(hitloc, force, 2, bound(0, damage, 200) / 16, self, attacker);
+               if (damage > 50)
+                       Violence_GibSplash_At(hitloc, force * -0.1, 3, 1, self, attacker);
+               if (damage > 100)
+                       Violence_GibSplash_At(hitloc, force * -0.2, 3, 1, self, attacker);
+       }
+               
+       if(self.health <= 0)
+       {        
+               if(self.sprite)
+               {
+                       // Update one more time to avoid waypoint fading without emptying healthbar
+                       WaypointSprite_UpdateHealth(self.sprite, 0);
+               }
+               
+               if(deathtype == DEATH_KILL)
+                       self.candrop = FALSE; // killed by mobkill command
+                       
+               // TODO: fix this?
+               activator = attacker;
+               other = self.enemy;
+               SUB_UseTargets();
+               self.target2 = self.oldtarget2; // reset to original target on death, incase we respawn
+       
+               monster_die();
+               
+               frag_attacker = attacker;
+               frag_target = self;
+               MUTATOR_CALLHOOK(MonsterDies);
+               
+               if(self.health <= -100) // check if we're already gibbed
+               {
+                       Violence_GibSplash(self, 1, 0.5, attacker);
+               
+                       self.think = SUB_Remove;
+                       self.nextthink = time + 0.1;
+               }
+       }
+       
+       self.SendFlags |= MSF_STATUS;
+}
+
+void monster_think()
+{
+       self.think = monster_think;
+       self.nextthink = self.ticrate;
+       
+       MON_ACTION(self.monsterid, MR_THINK);
+}
+
+void monster_spawn()
+{
+       MON_ACTION(self.monsterid, MR_SETUP);
+
+       if not(self.monster_respawned)
+               Monster_CheckMinibossFlag();
+       
+       self.max_health = self.health;
+       self.pain_finished = self.nextthink;
+       self.anim_start_time = time;
+       
+       if not(self.noalign)
+       {
+               setorigin(self, self.origin + '0 0 20');
+               tracebox(self.origin + '0 0 100', self.mins, self.maxs, self.origin - '0 0 10000', MOVE_WORLDONLY, self);
+               setorigin(self, trace_endpos);
+       }
+       
+       if not(self.monster_respawned)
+       if not(self.skin)
+               self.skin = rint(random() * 4);
+       
+       self.pos1 = self.origin;
+       
+       monster_setupsounds(self.netname);
+
+       monster_precachesounds(self);
+       
+       if(teamplay)
+               self.monster_attack = TRUE; // we can have monster enemies in team games
+               
+       if(autocvar_g_monsters_healthbars)
+       {
+               WaypointSprite_Spawn(strzone(strdecolorize(self.monster_name)), 0, 600, self, '0 0 1' * (self.maxs_z + 15), world, 0, self, sprite, FALSE, RADARICON_DANGER, ((self.team) ? Team_ColorRGB(self.team) : '1 0 0'));       
+               WaypointSprite_UpdateMaxHealth(self.sprite, self.max_health);
+               WaypointSprite_UpdateHealth(self.sprite, self.health);
+       }
+       
+       monster_sound(self.msound_spawn, 0, FALSE);
+
+       MUTATOR_CALLHOOK(MonsterSpawn);
+       
+       self.think = monster_think;
+       self.nextthink = time + self.ticrate;
+       
+       self.SendFlags = MSF_SETUP;
+}
+
+float monster_initialize(float mon_id, float nodrop)
+{
+       if not(autocvar_g_monsters)
+               return FALSE;
+               
+       vector min_s, max_s;
+       entity mon = get_monsterinfo(mon_id);
+       
+       // support for quake style removing monsters based on skill
+       if(monster_skill <= autocvar_g_monsters_skill_easy && (self.spawnflags & MONSTERSKILL_NOTEASY)) { return FALSE; }
+       if(monster_skill == autocvar_g_monsters_skill_normal && (self.spawnflags & MONSTERSKILL_NOTMEDIUM)) { return FALSE; }
+       if(monster_skill == autocvar_g_monsters_skill_hard && (self.spawnflags & MONSTERSKILL_NOTHARD)) { return FALSE; }
+       if(monster_skill == autocvar_g_monsters_skill_insane && (self.spawnflags & MONSTERSKILL_NOTINSANE)) { return FALSE; }
+       if(monster_skill >= autocvar_g_monsters_skill_nightmare && (self.spawnflags & MONSTERSKILL_NOTNIGHTMARE)) { return FALSE; }
+
+       if(self.monster_name == "")
+               self.monster_name = M_NAME(mon_id);
+       
+       if(self.team && !teamplay)
+               self.team = 0;
+
+       self.flags = FL_MONSTER;
+               
+       if not(self.spawnflags & MONSTERFLAG_SPAWNED) // naturally spawned monster
+       if not(self.monster_respawned)
+               monsters_total += 1;
+               
+       min_s = mon.mins;
+       max_s = mon.maxs;
+       
+       self.netname = mon.netname;
+
+       setsize(self, min_s, max_s);
+       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.solid                              = SOLID_BBOX;
+       self.movetype                   = MOVETYPE_WALK;
+       self.spawnshieldtime    = time + autocvar_g_monsters_spawnshieldtime;
+       monsters_spawned           += 1;
+       self.enemy                              = world;
+       self.velocity                   = '0 0 0';
+       self.moveto                             = self.origin;
+       self.pos2                               = self.angles;
+       self.reset                              = monsters_reset;
+       self.candrop                    = TRUE;
+       self.view_ofs                   = '0 0 1' * (self.maxs_z * 0.5);
+       self.oldtarget2                 = self.target2;
+       self.deadflag                   = DEAD_NO;
+       self.noalign                    = nodrop;
+       self.spawn_time                 = time;
+       self.gravity                    = 1;
+       self.dphitcontentsmask  = DPCONTENTS_SOLID | DPCONTENTS_BODY | DPCONTENTS_BOTCLIP | DPCONTENTS_MONSTERCLIP;
+       
+       if(mon.spawnflags & MONSTER_TYPE_SWIM)
+               self.flags |= FL_SWIM;
+               
+       if(mon.spawnflags & MONSTER_TYPE_FLY)
+       {
+               self.flags |= FL_FLY;
+               self.movetype = MOVETYPE_FLY;
+       }
+       
+       if not(self.scale)
+               self.scale = 1;
+       
+       if not(self.attack_range)
+               self.attack_range = 120;
+       
+       if not(self.ticrate)
+               self.ticrate = autocvar_g_monsters_think_delay;
+               
+       self.ticrate = bound(sys_frametime, self.ticrate, 60);
+       
+       if not(self.armorvalue)
+               self.armorvalue = 1; // multiplier
+       
+       if not(self.target_range)
+               self.target_range = autocvar_g_monsters_target_range;
+       
+       if not(self.respawntime)
+               self.respawntime = autocvar_g_monsters_respawn_delay;
+       
+       if not(self.monster_moveflags)
+               self.monster_moveflags = MONSTER_MOVE_WANDER;
+       
+       monster_link(monster_spawn);
+
+       return TRUE;
+}
diff --git a/qcsrc/common/monsters/lib/monsters_early.qh b/qcsrc/common/monsters/lib/monsters_early.qh
new file mode 100644 (file)
index 0000000..f690cdc
--- /dev/null
@@ -0,0 +1,35 @@
+// for definitions used outside the monsters folder
+
+#ifdef SVQC
+.string spawnmob;
+.float monster_attack;
+
+float monster_skill;
+float spawncode_first_load; // used to tell the player the monster database is loading (TODO: fix this?)
+
+.entity monster_owner; // new monster owner entity, fixes non-solid monsters
+.float monstercount; // per player monster count
+
+.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
+
+#endif // SVQC
+
+#ifndef MENUQC
+
+.float anim_start_time;
+
+float MSF_UPDATE       = 2;
+float MSF_STATUS       = 4;
+float MSF_SETUP        = 8;
+float MSF_ANG          = 16;
+float MSF_MOVE         = 32;
+float MSF_ANIM         = 64;
+
+float MSF_FULL_UPDATE  = 16777215;
+
+#endif // CSQC/SVQC
diff --git a/qcsrc/common/monsters/lib/spawn.qc b/qcsrc/common/monsters/lib/spawn.qc
new file mode 100644 (file)
index 0000000..95e0261
--- /dev/null
@@ -0,0 +1,65 @@
+entity spawnmonster (string monster, float mnster, entity spawnedby, entity own, vector orig, float respwn, float moveflag)
+{
+       if(!spawncode_first_load)
+       {
+               initialize_field_db();
+               spawncode_first_load = TRUE;
+       }
+       
+       entity e = spawn();
+       
+       e.spawnflags = MONSTERFLAG_SPAWNED;
+       
+       if not(respwn)
+               e.spawnflags |= MONSTERFLAG_NORESPAWN;
+       
+       setorigin(e, orig);
+       
+       if(monster != "")
+       {
+               float i, found = 0;
+               entity mon;
+               for(i = MON_FIRST; i <= MON_LAST; ++i)
+               {
+                       mon = get_monsterinfo(i);
+                       if(mon.netname == monster)
+                       {
+                               found = TRUE;
+                               break;
+                       }
+               }
+               if not(found)
+                       monster = (get_monsterinfo(MON_FIRST)).netname;
+       }
+               
+       if(monster == "")
+       if(mnster)
+               monster = (get_monsterinfo(mnster)).netname;
+       
+       e.realowner = spawnedby;
+       
+       if(moveflag)
+               e.monster_moveflags = moveflag;
+       
+       if(IS_PLAYER(spawnedby))
+       {
+               if(teamplay && autocvar_g_monsters_teams)
+                       e.team = spawnedby.team; // colors handled in spawn code
+                       
+               if(e.team)
+                       e.colormap = 1024;
+               else
+                       e.colormap = spawnedby.colormap;
+                       
+               if(autocvar_g_monsters_owners)
+                       e.monster_owner = own; // using .owner makes the monster non-solid for its master
+                       
+               e.angles = spawnedby.angles;
+       }
+               
+       monster = strcat("$ spawnfunc_monster_", monster);
+               
+       target_spawn_edit_entity(e, monster, world, world, world, world, world);
+               
+       return e;
+}
diff --git a/qcsrc/common/monsters/monster/animus.qc b/qcsrc/common/monsters/monster/animus.qc
new file mode 100644 (file)
index 0000000..bd7639c
--- /dev/null
@@ -0,0 +1,143 @@
+#ifdef REGISTER_MONSTER
+REGISTER_MONSTER(
+/* MON_##id   */ ANIMUS,
+/* function   */ m_animus,
+/* spawnflags */ MONSTER_SIZE_BROKEN,
+/* mins,maxs  */ '-41 -41 -31', '41 41 31',
+/* model      */ "demon.mdl",
+/* netname    */ "animus",
+/* fullname   */ _("Animus")
+);
+
+#else
+#ifdef SVQC
+float autocvar_g_monster_animus;
+float autocvar_g_monster_animus_health;
+float autocvar_g_monster_animus_attack_jump_damage;
+float autocvar_g_monster_animus_damage;
+float autocvar_g_monster_animus_speed_walk;
+float autocvar_g_monster_animus_speed_run;
+
+const float animus_anim_stand  = 0;
+const float animus_anim_walk   = 1;
+const float animus_anim_run            = 2;
+const float animus_anim_leap   = 3;
+const float animus_anim_pain   = 4;
+const float animus_anim_death  = 5;
+const float animus_anim_attack = 6;
+
+void animus_touch_jump()
+{
+       if (self.health <= 0)
+               return;
+
+       if (monster_isvalidtarget(other, self))
+       {
+               if (vlen(self.velocity) > 300)
+               {
+                       Damage(other, self, self, autocvar_g_monster_animus_attack_jump_damage * monster_skill, DEATH_MONSTER_ANIMUS, other.origin, normalize(other.origin - self.origin));
+                       self.touch = MonsterTouch; // instantly turn it off to stop damage spam
+               }
+       }
+
+       if(trace_dphitcontents)
+               self.touch = MonsterTouch;
+}
+
+float animus_attack(float attack_type)
+{
+       switch(attack_type)
+       {
+               case MONSTER_ATTACK_MELEE:
+               {
+                       monsters_setframe(animus_anim_attack);
+                       self.attack_finished_single = time + 1;
+                       monster_melee(self.enemy, autocvar_g_monster_animus_damage, 0.3, DEATH_MONSTER_ANIMUS, TRUE);
+                       
+                       return TRUE;
+               }
+               case MONSTER_ATTACK_RANGED:
+               {
+                       makevectors(self.angles);
+                       if(monster_leap(animus_anim_leap, animus_touch_jump, v_forward * 700 + '0 0 300', 0.8))
+                               return TRUE;
+               }
+       }
+       
+       return FALSE;
+}
+
+void spawnfunc_monster_animus()
+{
+       if not(autocvar_g_monster_animus) { remove(self); return; }
+       
+       self.classname = "monster_animus";
+       
+       self.monster_spawnfunc = spawnfunc_monster_animus;
+       
+       if(Monster_CheckAppearFlags(self))
+               return;
+       
+       if not(monster_initialize(MON_ANIMUS, FALSE)) { remove(self); return; }
+}
+
+// compatibility with old spawns
+void spawnfunc_monster_demon1() { spawnfunc_monster_animus(); }
+void spawnfunc_monster_demon() { spawnfunc_monster_animus(); }
+
+float m_animus(float req)
+{
+       switch(req)
+       {
+               case MR_THINK:
+               {
+                       monster_move(autocvar_g_monster_animus_speed_run, autocvar_g_monster_animus_speed_walk, 100, animus_anim_run, animus_anim_walk, animus_anim_stand);
+                       return TRUE;
+               }
+               case MR_DEATH:
+               {
+                       monsters_setframe(animus_anim_death);
+                       return TRUE;
+               }
+               case MR_SETUP:
+               {
+                       if not(self.health) self.health = autocvar_g_monster_animus_health;
+                               
+                       self.monster_attackfunc = animus_attack;
+                       monsters_setframe(animus_anim_stand);
+                       
+                       return TRUE;
+               }
+               case MR_INIT:
+               {
+                       // nothing
+                       return TRUE;
+               }
+       }
+       
+       return TRUE;
+}
+
+#endif // SVQC
+#ifdef CSQC
+float m_animus(float req)
+{
+       switch(req)
+       {
+               case MR_DEATH:
+               {
+                       // nothing
+                       return TRUE;
+               }
+               case MR_INIT:
+               {
+                       precache_model ("models/monsters/demon.mdl");
+                       return TRUE;
+               }
+       }
+       
+       return TRUE;
+}
+
+#endif // CSQC
+#endif // REGISTER_MONSTER
diff --git a/qcsrc/common/monsters/monster/bruiser.qc b/qcsrc/common/monsters/monster/bruiser.qc
new file mode 100644 (file)
index 0000000..fe16f92
--- /dev/null
@@ -0,0 +1,125 @@
+#ifdef REGISTER_MONSTER
+REGISTER_MONSTER(
+/* MON_##id   */ BRUISER,
+/* function   */ m_bruiser,
+/* spawnflags */ MONSTER_SIZE_BROKEN,
+/* mins,maxs  */ '-20 -20 -31', '20 20 53',
+/* model      */ "knight.mdl",
+/* netname    */ "bruiser",
+/* fullname   */ _("Bruiser")
+);
+
+#else
+#ifdef SVQC
+float autocvar_g_monster_bruiser;
+float autocvar_g_monster_bruiser_health;
+float autocvar_g_monster_bruiser_melee_damage;
+float autocvar_g_monster_bruiser_speed_walk;
+float autocvar_g_monster_bruiser_speed_run;
+
+const float bruiser_anim_stand                 = 0;
+const float bruiser_anim_run           = 1;
+const float bruiser_anim_runattack     = 2;
+const float bruiser_anim_pain1                 = 3;
+const float bruiser_anim_pain2                 = 4;
+const float bruiser_anim_attack        = 5;
+const float bruiser_anim_walk          = 6;
+const float bruiser_anim_kneel                 = 7;
+const float bruiser_anim_standing      = 8;
+const float bruiser_anim_death1        = 9;
+const float bruiser_anim_death2        = 10;
+
+float bruiser_attack(float attack_type)
+{
+       switch(attack_type)
+       {
+               case MONSTER_ATTACK_MELEE:
+               {
+                       float len = vlen(self.velocity);
+                       monsters_setframe((len < 50) ? bruiser_anim_attack : bruiser_anim_runattack);
+                       self.attack_finished_single = time + 1.25;
+                       
+                       monster_melee(self.enemy, autocvar_g_monster_bruiser_melee_damage, 0.3, DEATH_MONSTER_BRUISER, FALSE);
+                       
+                       return TRUE;
+               }
+               case MONSTER_ATTACK_RANGED:
+               {
+                       // no ranged attacks for bruiser
+                       return FALSE;
+               }
+       }
+       
+       return FALSE;
+}
+
+void spawnfunc_monster_bruiser()
+{
+       if not(autocvar_g_monster_bruiser) { remove(self); return; }
+       
+       self.classname = "monster_bruiser";
+       
+       self.monster_spawnfunc = spawnfunc_monster_bruiser;
+       
+       if(Monster_CheckAppearFlags(self))
+               return;
+       
+       if not(monster_initialize(MON_BRUISER, FALSE)) { remove(self); return; }
+}
+
+float m_bruiser(float req)
+{
+       switch(req)
+       {
+               case MR_THINK:
+               {
+                       monster_move(autocvar_g_monster_bruiser_speed_run, autocvar_g_monster_bruiser_speed_walk, 50, bruiser_anim_run, bruiser_anim_walk, bruiser_anim_stand);
+                       return TRUE;
+               }
+               case MR_DEATH:
+               {
+                       monsters_setframe((random() > 0.5) ? bruiser_anim_death1 : bruiser_anim_death2);
+                       return TRUE;
+               }
+               case MR_SETUP:
+               {
+                       if not(self.health) self.health = autocvar_g_monster_bruiser_health;
+                               
+                       self.monster_attackfunc = bruiser_attack;
+                       monsters_setframe(bruiser_anim_stand);
+                       
+                       return TRUE;
+               }
+               case MR_INIT:
+               {
+                       // nothing
+                       return TRUE;
+               }
+       }
+       
+       return TRUE;
+}
+
+#endif // SVQC
+#ifdef CSQC
+float m_bruiser(float req)
+{
+       switch(req)
+       {
+               case MR_DEATH:
+               {
+                       // nothing
+                       return TRUE;
+               }
+               case MR_INIT:
+               {
+                       precache_model ("models/monsters/knight.mdl");
+                       return TRUE;
+               }
+       }
+       
+       return TRUE;
+}
+
+#endif // CSQC
+#endif // REGISTER_MONSTER
diff --git a/qcsrc/common/monsters/monster/brute.qc b/qcsrc/common/monsters/monster/brute.qc
new file mode 100644 (file)
index 0000000..226e0d5
--- /dev/null
@@ -0,0 +1,255 @@
+#ifdef REGISTER_MONSTER
+REGISTER_MONSTER(
+/* MON_##id   */ BRUTE,
+/* function   */ m_brute,
+/* spawnflags */ 0,
+/* mins,maxs  */ '-36 -36 -20', '36 36 50',
+/* model      */ "ogre.dpm",
+/* netname    */ "brute",
+/* fullname   */ _("Brute")
+);
+
+#else
+#ifdef SVQC
+float autocvar_g_monster_brute;
+float autocvar_g_monster_brute_health;
+float autocvar_g_monster_brute_chainsaw_damage;
+float autocvar_g_monster_brute_speed_walk;
+float autocvar_g_monster_brute_speed_run;
+float autocvar_g_monster_brute_attack_uzi_bullets;
+float autocvar_g_monster_brute_attack_uzi_damage;
+float autocvar_g_monster_brute_attack_uzi_force;
+float autocvar_g_monster_brute_attack_uzi_chance;
+float autocvar_g_monster_brute_attack_grenade_damage;
+float autocvar_g_monster_brute_attack_grenade_edgedamage;
+float autocvar_g_monster_brute_attack_grenade_force;
+float autocvar_g_monster_brute_attack_grenade_radius;
+
+const float brute_anim_idle            = 0;
+const float brute_anim_walk            = 1;
+const float brute_anim_run                     = 2;
+const float brute_anim_pain            = 3;
+const float brute_anim_swing           = 4;
+const float brute_anim_die                     = 5;
+
+.float brute_cycles;
+
+void brute_blade()
+{
+       self.brute_cycles += 1;
+       self.angles_y = self.angles_y + random()* 25;
+       
+       monster_melee(self.enemy, autocvar_g_monster_brute_chainsaw_damage, 0.3, DEATH_MONSTER_BRUTE_BLADE, TRUE);
+       
+       if(self.brute_cycles <= 4)
+               defer(0.2, brute_blade);
+}
+
+void brute_uzi()
+{
+       self.brute_cycles += 1;
+       
+       monster_makevectors(self.enemy);
+       
+       W_SetupShot (self, autocvar_g_antilag_bullets && 18000 >= autocvar_g_antilag_bullets, 0, "weapons/uzi_fire.wav", CH_WEAPON_A, autocvar_g_monster_brute_attack_uzi_damage);
+       fireBallisticBullet(w_shotorg, w_shotdir, 0.02, 18000, 5, autocvar_g_monster_brute_attack_uzi_damage, autocvar_g_monster_brute_attack_uzi_force, DEATH_MONSTER_BRUTE_UZI, 0, 1, 115);
+       endFireBallisticBullet();
+       
+       if(self.brute_cycles <= autocvar_g_monster_brute_attack_uzi_bullets)
+               defer(0.1, brute_uzi);
+}
+
+void brute_grenade_explode()
+{
+       pointparticles(particleeffectnum("grenade_explode"), self.origin, '0 0 0', 1);
+       sound(self, CH_SHOTS, "weapons/grenade_impact.wav", VOL_BASE, ATTN_NORM);
+
+       self.event_damage = func_null;
+       self.takedamage = DAMAGE_NO;
+
+       if(self.movetype == MOVETYPE_NONE)
+               self.velocity = self.oldvelocity;
+
+       RadiusDamage (self, self.realowner, autocvar_g_monster_brute_attack_grenade_damage, autocvar_g_monster_brute_attack_grenade_edgedamage, autocvar_g_monster_brute_attack_grenade_radius, world, autocvar_g_monster_brute_attack_grenade_force, self.projectiledeathtype, other);
+
+       remove (self);
+}
+
+void brute_grenade_damage(entity inflictor, entity attacker, float damage, float deathtype, vector hitloc, vector force)
+{
+       if (self.health <= 0)
+               return;
+               
+       if (!W_CheckProjectileDamage(inflictor.realowner, self.realowner, deathtype, -1)) // no exceptions
+               return; // g_projectiles_damage says to halt
+               
+       self.health = self.health - damage;
+       
+       if (self.health <= 0)
+               W_PrepareExplosionByDamage(attacker, self.use);
+}
+
+void brute_grenade_touch()
+{
+       PROJECTILE_TOUCH;
+       
+       self.use ();
+}
+
+void brute_grenade_think()
+{
+       self.nextthink = time;
+       if (time > self.cnt)
+       {
+               other = world;
+               brute_grenade_explode();
+               return;
+       }
+}
+
+void brute_grenade()
+{
+       entity gren;
+
+       W_SetupShot_ProjectileSize (self, '-3 -3 -3', '3 3 3', FALSE, 4, "weapons/grenade_fire.wav", CH_WEAPON_A, autocvar_g_monster_brute_attack_grenade_damage);
+       w_shotdir = v_forward; // no TrueAim for grenades please
+
+       gren = spawn ();
+       gren.owner = gren.realowner = self;
+       gren.classname = "grenade";
+       gren.bot_dodge = TRUE;
+       gren.bot_dodgerating = autocvar_g_monster_brute_attack_grenade_damage;
+       gren.movetype = MOVETYPE_BOUNCE;
+       PROJECTILE_MAKETRIGGER(gren);
+       gren.projectiledeathtype = DEATH_MONSTER_BRUTE_GRENADE;
+       setorigin(gren, w_shotorg);
+       setsize(gren, '-3 -3 -3', '3 3 3');
+
+       gren.cnt = time + 5;
+       gren.nextthink = time;
+       gren.think = brute_grenade_think;
+       gren.use = brute_grenade_explode;
+       gren.touch = brute_grenade_touch;
+
+       gren.takedamage = DAMAGE_YES;
+       gren.health = autocvar_g_balance_grenadelauncher_primary_health;
+       gren.damageforcescale = autocvar_g_balance_grenadelauncher_primary_damageforcescale;
+       gren.event_damage = brute_grenade_damage;
+       gren.damagedbycontents = TRUE;
+       gren.missile_flags = MIF_SPLASH | MIF_ARC;
+       W_SETUPPROJECTILEVELOCITY_UP(gren, g_balance_grenadelauncher_primary);
+
+       gren.angles = vectoangles (gren.velocity);
+       gren.flags = FL_PROJECTILE;
+
+       CSQCProjectile(gren, TRUE, PROJECTILE_GRENADE, TRUE);
+}
+
+float brute_attack(float attack_type)
+{
+       switch(attack_type)
+       {
+               case MONSTER_ATTACK_MELEE:
+               {
+                       self.brute_cycles = 0;
+                       monsters_setframe(brute_anim_swing);
+                       self.attack_finished_single = time + 1.3;
+                       brute_blade();
+                       
+                       return TRUE;
+               }
+               case MONSTER_ATTACK_RANGED:
+               {
+                       self.brute_cycles = 0;
+                       if(random() <= autocvar_g_monster_brute_attack_uzi_chance)
+                       {
+                               monsters_setframe(brute_anim_pain);
+                               self.attack_finished_single = time + 0.8;
+                               defer(0.1, brute_uzi);
+                       }
+                       else
+                       {
+                               monster_makevectors(self.enemy);
+                               brute_grenade();
+                               monsters_setframe(brute_anim_pain);
+                               self.attack_finished_single = time + 1.2;
+                       }
+                       
+                       return TRUE;
+               }
+       }
+       
+       return FALSE;
+}
+
+void spawnfunc_monster_brute()
+{
+       if not(autocvar_g_monster_brute) { remove(self); return; }
+       
+       self.classname = "monster_brute";
+       
+       self.monster_spawnfunc = spawnfunc_monster_brute;
+       
+       if(Monster_CheckAppearFlags(self))
+               return;
+       
+       if not(monster_initialize(MON_BRUTE, FALSE)) { remove(self); return; }
+}
+
+float m_brute(float req)
+{
+       switch(req)
+       {
+               case MR_THINK:
+               {
+                       monster_move(autocvar_g_monster_brute_speed_run, autocvar_g_monster_brute_speed_walk, 300, brute_anim_run, brute_anim_walk, brute_anim_idle);
+                       return TRUE;
+               }
+               case MR_DEATH:
+               {
+                       monsters_setframe(brute_anim_die);
+                       return TRUE;
+               }
+               case MR_SETUP:
+               {
+                       if not(self.health) self.health = autocvar_g_monster_brute_health;
+                               
+                       self.monster_attackfunc = brute_attack;
+                       monsters_setframe(brute_anim_idle);
+                       self.weapon = WEP_GRENADE_LAUNCHER;
+                       
+                       return TRUE;
+               }
+               case MR_INIT:
+               {
+                       // nothing
+                       return TRUE;
+               }
+       }
+       
+       return TRUE;
+}
+
+#endif // SVQC
+#ifdef CSQC
+float m_brute(float req)
+{
+       switch(req)
+       {
+               case MR_DEATH:
+               {
+                       // nothing
+                       return TRUE;
+               }
+               case MR_INIT:
+               {
+                       precache_model ("models/monsters/ogre.dpm");
+                       return TRUE;
+               }
+       }
+       
+       return TRUE;
+}
+
+#endif // CSQC
+#endif // REGISTER_MONSTER
diff --git a/qcsrc/common/monsters/monster/cerberus.qc b/qcsrc/common/monsters/monster/cerberus.qc
new file mode 100644 (file)
index 0000000..907c07f
--- /dev/null
@@ -0,0 +1,136 @@
+#ifdef REGISTER_MONSTER
+REGISTER_MONSTER(
+/* MON_##id   */ CERBERUS,
+/* function   */ m_cerberus,
+/* spawnflags */ 0,
+/* mins,maxs  */ '-16 -16 -24', '16 16 12',
+/* model      */ "dog.dpm",
+/* netname    */ "cerberus",
+/* fullname   */ _("Cerberus")
+);
+
+#else
+#ifdef SVQC
+float autocvar_g_monster_cerberus;
+float autocvar_g_monster_cerberus_health;
+float autocvar_g_monster_cerberus_bite_damage;
+float autocvar_g_monster_cerberus_attack_jump_damage;
+float autocvar_g_monster_cerberus_speed_walk;
+float autocvar_g_monster_cerberus_speed_run;
+
+const float cerberus_anim_idle         = 0;
+const float cerberus_anim_walk         = 1;
+const float cerberus_anim_run          = 2;
+const float cerberus_anim_attack       = 3;
+const float cerberus_anim_die          = 4;
+const float cerberus_anim_pain         = 5;
+
+void cerberus_touch_jump()
+{
+       if (other.takedamage)
+       if (vlen(self.velocity) > 300)
+       {
+               Damage(self.enemy, self, self, autocvar_g_monster_cerberus_attack_jump_damage * monster_skill, DEATH_MONSTER_CERBERUS_JUMP, self.enemy.origin, normalize(self.enemy.origin - self.origin));
+               self.touch = MonsterTouch;
+       }
+
+       if(trace_dphitcontents)
+               self.touch = MonsterTouch;
+}
+
+float cerberus_attack(float attack_type)
+{
+       switch(attack_type)
+       {
+               case MONSTER_ATTACK_MELEE:
+               {
+                       monsters_setframe(cerberus_anim_attack);
+                       self.attack_finished_single = time + 0.7;
+                       monster_melee(self.enemy, autocvar_g_monster_cerberus_bite_damage, 0.2, DEATH_MONSTER_CERBERUS_BITE, TRUE);
+                       
+                       return TRUE;
+               }
+               case MONSTER_ATTACK_RANGED:
+               {
+                       makevectors(self.angles);
+                       if(monster_leap(cerberus_anim_attack, cerberus_touch_jump, v_forward * 300 + '0 0 200', 0.8))
+                               return TRUE;
+               }
+       }
+       
+       return FALSE;
+}
+
+void spawnfunc_monster_cerberus()
+{
+       if not(autocvar_g_monster_cerberus) { remove(self); return; }
+       
+       self.classname = "monster_cerberus";
+       
+       self.monster_spawnfunc = spawnfunc_monster_cerberus;
+       
+       if(Monster_CheckAppearFlags(self))
+               return;
+       
+       if not(monster_initialize(MON_CERBERUS, FALSE)) { remove(self); return; }
+}
+
+// compatibility with old spawns
+void spawnfunc_monster_dog() { spawnfunc_monster_cerberus(); }
+
+float m_cerberus(float req)
+{
+       switch(req)
+       {
+               case MR_THINK:
+               {
+                       monster_move(autocvar_g_monster_cerberus_speed_run, autocvar_g_monster_cerberus_speed_walk, 50, cerberus_anim_run, cerberus_anim_walk, cerberus_anim_idle);
+                       return TRUE;
+               }
+               case MR_DEATH:
+               {
+                       monsters_setframe(cerberus_anim_die);
+                       return TRUE;
+               }
+               case MR_SETUP:
+               {
+                       if not(self.health) self.health = autocvar_g_monster_cerberus_health;
+                               
+                       self.monster_attackfunc = cerberus_attack;
+                       monsters_setframe(cerberus_anim_idle);
+                       
+                       return TRUE;
+               }
+               case MR_INIT:
+               {
+                       // nothing
+                       return TRUE;
+               }
+       }
+       
+       return TRUE;
+}
+
+#endif // SVQC
+#ifdef CSQC
+float m_cerberus(float req)
+{
+       switch(req)
+       {
+               case MR_DEATH:
+               {
+                       // nothing
+                       return TRUE;
+               }
+               case MR_INIT:
+               {
+                       precache_model ("models/monsters/dog.dpm");
+                       return TRUE;
+               }
+       }
+       
+       return TRUE;
+}
+
+#endif // CSQC
+#endif // REGISTER_MONSTER
\ No newline at end of file
diff --git a/qcsrc/common/monsters/monster/knight.qc b/qcsrc/common/monsters/monster/knight.qc
new file mode 100644 (file)
index 0000000..bc2a0e9
--- /dev/null
@@ -0,0 +1,334 @@
+#ifdef REGISTER_MONSTER
+REGISTER_MONSTER(
+/* MON_##id   */ KNIGHT,
+/* function   */ m_knight,
+/* spawnflags */ MONSTER_SIZE_BROKEN,
+/* mins,maxs  */ '-20 -20 -32', '20 20 41',
+/* model      */ "hknight.mdl",
+/* netname    */ "knight",
+/* fullname   */ _("Knight")
+);
+
+#else
+#ifdef SVQC
+float autocvar_g_monster_knight;
+float autocvar_g_monster_knight_health;
+float autocvar_g_monster_knight_melee_damage;
+float autocvar_g_monster_knight_inferno_damage;
+float autocvar_g_monster_knight_inferno_damagetime;
+float autocvar_g_monster_knight_inferno_chance;
+float autocvar_g_monster_knight_speed_walk;
+float autocvar_g_monster_knight_speed_run;
+float autocvar_g_monster_knight_fireball_damage;
+float autocvar_g_monster_knight_fireball_force;
+float autocvar_g_monster_knight_fireball_radius;
+float autocvar_g_monster_knight_fireball_chance;
+float autocvar_g_monster_knight_fireball_edgedamage;
+float autocvar_g_monster_knight_spike_chance;
+float autocvar_g_monster_knight_spike_force;
+float autocvar_g_monster_knight_spike_radius;
+float autocvar_g_monster_knight_spike_edgedamage;
+float autocvar_g_monster_knight_spike_damage;
+float autocvar_g_monster_knight_jump_chance;
+float autocvar_g_monster_knight_jump_damage;
+float autocvar_g_monster_knight_jump_dist;
+
+const float knight_anim_stand  = 0;
+const float knight_anim_walk   = 1;
+const float knight_anim_run    = 2;
+const float knight_anim_pain   = 3;
+const float knight_anim_death1         = 4;
+const float knight_anim_death2         = 5;
+const float knight_anim_charge1 = 6;
+const float knight_anim_magic1         = 7;
+const float knight_anim_magic2         = 8;
+const float knight_anim_charge2 = 9;
+const float knight_anim_slice  = 10;
+const float knight_anim_smash  = 11;
+const float knight_anim_wattack = 12;
+const float knight_anim_magic3         = 13;
+
+.float knight_cycles;
+
+void knight_inferno()
+{
+       if not(self.enemy)
+               return;
+               
+       traceline((self.absmin + self.absmax) * 0.5, (self.enemy.absmin + self.enemy.absmax) * 0.5, TRUE, world);
+       if (trace_fraction != 1)
+               return; // not visible
+       
+       self.enemy.effects |= EF_MUZZLEFLASH;
+       sound(self.enemy, CHAN_AUTO, "player/lava.wav", 1, ATTN_NORM);
+       
+       if(vlen(self.enemy.origin - self.origin) <= 2000)
+               Fire_AddDamage(self.enemy, self, autocvar_g_monster_knight_inferno_damage * monster_skill, autocvar_g_monster_knight_inferno_damagetime, DEATH_MONSTER_KNIGHT_INFERNO);
+}
+
+void knight_fireball_explode()
+{
+       entity e;
+       if(self)
+       {
+               pointparticles(particleeffectnum("fireball_explode"), self.origin, '0 0 0', 1);
+               
+               RadiusDamage(self, self.realowner, autocvar_g_monster_knight_fireball_damage, autocvar_g_monster_knight_fireball_edgedamage, autocvar_g_monster_knight_fireball_force, world, autocvar_g_monster_knight_fireball_radius, self.projectiledeathtype, world);
+               
+               for(e = world; (e = findfloat(e, takedamage, DAMAGE_AIM)); ) if(vlen(e.origin - self.origin) <= autocvar_g_monster_knight_fireball_radius)
+                       Fire_AddDamage(e, self, 5 * monster_skill, autocvar_g_monster_knight_inferno_damagetime, self.projectiledeathtype);
+               
+               remove(self);
+       }
+}
+
+void knight_fireball_touch()
+{
+       PROJECTILE_TOUCH;
+       
+       knight_fireball_explode();
+}
+
+void knight_fireball()
+{
+       entity missile = spawn();
+       vector dir = normalize((self.enemy.origin + '0 0 10') - self.origin);
+       
+       monster_makevectors(self.enemy);
+       
+       self.effects |= EF_MUZZLEFLASH;
+       sound(self, CHAN_WEAPON, "weapons/fireball2.wav", 1, ATTN_NORM);
+
+       missile.owner = missile.realowner = self;
+       missile.solid = SOLID_TRIGGER;
+       missile.movetype = MOVETYPE_FLYMISSILE;
+       missile.projectiledeathtype = DEATH_MONSTER_KNIGHT_FBALL;
+       setsize(missile, '-6 -6 -6', '6 6 6');          
+       setorigin(missile, self.origin + self.view_ofs + v_forward * 14);
+       missile.flags = FL_PROJECTILE;
+       missile.velocity = dir * 400;
+       missile.avelocity = '300 300 300';
+       missile.nextthink = time + 5;
+       missile.think = knight_fireball_explode;
+       missile.enemy = self.enemy;
+       missile.touch = knight_fireball_touch;
+       CSQCProjectile(missile, TRUE, PROJECTILE_FIREMINE, TRUE);
+}
+
+void knight_spike_explode()
+{
+       if(self)
+       {
+               pointparticles(particleeffectnum("TE_WIZSPIKE"), self.origin, '0 0 0', 1);
+               
+               RadiusDamage (self, self.realowner, autocvar_g_monster_knight_spike_damage, autocvar_g_monster_knight_spike_edgedamage, autocvar_g_monster_knight_spike_force, world, autocvar_g_monster_knight_spike_radius, DEATH_MONSTER_KNIGHT_SPIKE, other);
+               remove(self);
+       }
+}
+
+void knight_spike_touch()
+{
+       PROJECTILE_TOUCH;
+       
+       knight_spike_explode();
+}
+
+void knight_spike()
+{
+       entity missile;
+       vector dir = normalize((self.enemy.origin + '0 0 10') - self.origin);
+
+       self.effects |= EF_MUZZLEFLASH;
+
+       missile = spawn ();
+       missile.owner = missile.realowner = self;
+       missile.solid = SOLID_TRIGGER;
+       missile.movetype = MOVETYPE_FLYMISSILE;
+       setsize (missile, '0 0 0', '0 0 0');            
+       setorigin(missile, self.origin + '0 0 10' + v_forward * 14);
+       missile.scale = self.scale;
+       missile.flags = FL_PROJECTILE;
+       missile.velocity = dir * 400;
+       missile.avelocity = '300 300 300';
+       missile.nextthink = time + 5;
+       missile.think = knight_spike_explode;
+       missile.enemy = self.enemy;
+       missile.touch = knight_spike_touch;
+       CSQCProjectile(missile, TRUE, PROJECTILE_CRYLINK, TRUE);
+}
+
+void knight_spikes()
+{
+       self.knight_cycles += 1;
+       knight_spike();
+       
+       if(self.knight_cycles <= 7)
+               defer(0.1, knight_spikes);
+}
+
+float knight_attack_ranged()
+{
+       if not(self.flags & FL_ONGROUND)
+               return FALSE;
+               
+       self.knight_cycles = 0;
+       
+       RandomSelection_Init();
+       RandomSelection_Add(world, 1, "", autocvar_g_monster_knight_fireball_chance, 1);
+       RandomSelection_Add(world, 2, "", autocvar_g_monster_knight_inferno_chance, 1);
+       RandomSelection_Add(world, 3, "", autocvar_g_monster_knight_spike_chance, 1);
+       if(self.health >= 100) RandomSelection_Add(world, 4, "", ((vlen(self.enemy.origin - self.origin) > autocvar_g_monster_knight_jump_dist) ? 1 : autocvar_g_monster_knight_jump_chance), 1);
+       
+       switch(RandomSelection_chosen_float)
+       {
+               case 1:
+               {
+                       monsters_setframe(knight_anim_magic2);
+                       self.attack_finished_single = time + 2;
+                       defer(0.4, knight_fireball);
+                       
+                       return TRUE;
+               }
+               case 2:
+               {
+                       self.attack_finished_single = time + 3;
+                       defer(0.5, knight_inferno);
+                       return TRUE;
+               }
+               case 3:
+               {
+                       monsters_setframe(knight_anim_magic3);
+                       self.attack_finished_single = time + 3;
+                       defer(0.4, knight_spikes);
+                       
+                       return TRUE;
+               }
+               case 4:
+               {
+                       float er = vlen(self.enemy.origin - self.origin);
+                       
+                       if(er >= 400 && er < 1200)
+                       if(findtrajectorywithleading(self.origin, self.mins, self.maxs, self.enemy, 1000, 0, 10, 0, self))
+                       {
+                               self.velocity = findtrajectory_velocity;
+                               Damage(self.enemy, self, self, autocvar_g_monster_knight_jump_damage * monster_skill, DEATH_MONSTER_KNIGHT_CRUSH, self.enemy.origin, normalize(self.enemy.origin - self.origin));
+                               self.attack_finished_single = time + 2;
+                               return TRUE;
+                       }
+                       return FALSE;
+               }
+       }
+       
+       return FALSE;
+}
+
+float knight_attack(float attack_type)
+{
+       switch(attack_type)
+       {
+               case MONSTER_ATTACK_MELEE:
+               {
+                       float anim;
+                       if(random() < 0.3)
+                               anim = knight_anim_slice;
+                       else if(random() < 0.6)
+                               anim = knight_anim_smash;
+                       else
+                               anim = knight_anim_wattack;
+                       
+                       monsters_setframe(anim);
+                       self.attack_finished_single = time + 0.7;
+                       monster_melee(self.enemy, autocvar_g_monster_knight_melee_damage, 0.3, DEATH_MONSTER_KNIGHT_MELEE, TRUE);
+                       
+                       return TRUE;
+               }
+               case MONSTER_ATTACK_RANGED:
+               {
+                       if(knight_attack_ranged())
+                               return TRUE;
+               }
+       }
+       
+       return FALSE;
+}
+
+void spawnfunc_monster_knight()
+{
+       if not(autocvar_g_monster_knight) { remove(self); return; }
+       
+       self.classname = "monster_knight";
+       
+       self.monster_spawnfunc = spawnfunc_monster_knight;
+       
+       if(Monster_CheckAppearFlags(self))
+               return;
+       
+       if not(monster_initialize(MON_KNIGHT, FALSE)) { remove(self); return; }
+}
+
+// compatibility with old spawns
+void spawnfunc_monster_hell_knight() { spawnfunc_monster_knight(); }
+
+float m_knight(float req)
+{
+       switch(req)
+       {
+               case MR_THINK:
+               {
+                       monster_move(autocvar_g_monster_knight_speed_run, autocvar_g_monster_knight_speed_walk, 100, knight_anim_run, knight_anim_walk, knight_anim_stand);
+                       return TRUE;
+               }
+               case MR_DEATH:
+               {
+                       float chance = random();
+                       monsters_setframe((random() > 0.5) ? knight_anim_death1 : knight_anim_death2);
+                       if(chance < 0.10 || self.flags & MONSTERFLAG_MINIBOSS)
+                       if(self.candrop)
+                       {
+                               self.superweapons_finished = time + autocvar_g_balance_superweapons_time + 5; // give the player a few seconds to find the weapon
+                               self.weapon = WEP_FIREBALL;
+                       }
+                       return TRUE;
+               }
+               case MR_SETUP:
+               {
+                       if not(self.health) self.health = autocvar_g_monster_knight_health;
+                               
+                       self.monster_attackfunc = knight_attack;
+                       monsters_setframe(knight_anim_stand);
+                       
+                       return TRUE;
+               }
+               case MR_INIT:
+               {
+                       precache_sound ("player/lava.wav");
+                       return TRUE;
+               }
+       }
+       
+       return TRUE;
+}
+
+#endif // SVQC
+#ifdef CSQC
+float m_knight(float req)
+{
+       switch(req)
+       {
+               case MR_DEATH:
+               {
+                       // nothing
+                       return TRUE;
+               }
+               case MR_INIT:
+               {
+                       precache_model ("models/monsters/hknight.mdl");
+                       return TRUE;
+               }
+       }
+       
+       return TRUE;
+}
+
+#endif // CSQC
+#endif // REGISTER_MONSTER
diff --git a/qcsrc/common/monsters/monster/mage.qc b/qcsrc/common/monsters/monster/mage.qc
new file mode 100644 (file)
index 0000000..f62892f
--- /dev/null
@@ -0,0 +1,449 @@
+#ifdef REGISTER_MONSTER
+REGISTER_MONSTER(
+/* MON_##id   */ MAGE,
+/* function   */ m_mage,
+/* spawnflags */ 0,
+/* mins,maxs  */ '-36 -36 -24', '36 36 50',
+/* model      */ "mage.dpm",
+/* netname    */ "mage",
+/* fullname   */ _("Mage")
+);
+
+#else
+#ifdef SVQC
+float autocvar_g_monster_mage;
+float autocvar_g_monster_mage_health;
+float autocvar_g_monster_mage_speed;
+float autocvar_g_monster_mage_attack_spike_damage;
+float autocvar_g_monster_mage_attack_spike_radius;
+float autocvar_g_monster_mage_attack_spike_delay;
+float autocvar_g_monster_mage_attack_melee_damage;
+float autocvar_g_monster_mage_attack_melee_delay;
+float autocvar_g_monster_mage_heal_self;
+float autocvar_g_monster_mage_heal_friends;
+float autocvar_g_monster_mage_heal_minhealth;
+float autocvar_g_monster_mage_heal_range;
+float autocvar_g_monster_mage_heal_delay;
+float autocvar_g_monster_mage_shield_time;
+float autocvar_g_monster_mage_shield_delay;
+float autocvar_g_monster_mage_shield_blockpercent;
+float autocvar_g_monster_mage_attack_grenade_damage;
+float autocvar_g_monster_mage_attack_grenade_edgedamage;
+float autocvar_g_monster_mage_attack_grenade_radius;
+float autocvar_g_monster_mage_attack_grenade_lifetime;
+float autocvar_g_monster_mage_attack_grenade_force;
+float autocvar_g_monster_mage_attack_grenade_chance;
+
+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() mage_shield_die;
+
+float friend_needshelp(entity e)
+{
+       if(e == world)
+               return FALSE;
+       if(e.health <= 0)
+               return FALSE;
+       if(vlen(e.origin - self.origin) > autocvar_g_monster_mage_heal_range)
+               return FALSE;
+       if(IsDifferentTeam(e, self))
+               return FALSE;
+       if(e.frozen)
+               return FALSE;
+       if(!IS_PLAYER(e))
+               return (e.health < e.max_health);
+       if(e.items & IT_INVINCIBLE)
+               return FALSE;
+
+       switch(self.skin)
+       {
+               case 0:
+               {
+                       if(e.health < autocvar_g_balance_health_regenstable)
+                               return TRUE;
+                       break;
+               }
+               case 1:
+               {
+                       if((e.ammo_cells && e.ammo_cells < g_pickup_cells_max) || (e.ammo_rockets && e.ammo_rockets < g_pickup_rockets_max) || (e.ammo_nails && e.ammo_nails < g_pickup_nails_max) || (e.ammo_shells && e.ammo_shells < g_pickup_shells_max))
+                               return TRUE;
+                       break;
+               }
+               case 2:
+               {
+                       if(e.armorvalue < autocvar_g_balance_armor_regenstable)
+                               return TRUE;
+                       break;
+               }
+               case 3:
+               {
+                       if(e.health > 0)
+                               return TRUE;
+                       break;
+               }
+       }
+       
+       return FALSE;
+}
+
+void mageattack_melee()
+{
+       monster_melee(self.enemy, autocvar_g_monster_mage_attack_melee_damage, 0.3, DEATH_MONSTER_MAGE, TRUE);
+}
+
+void mage_grenade_explode()
+{
+       pointparticles(particleeffectnum("explosion_small"), self.origin, '0 0 0', 1);
+       
+       sound(self, CH_SHOTS, "weapons/grenade_impact.wav", VOL_BASE, ATTN_NORM);
+       RadiusDamage (self, self.realowner, autocvar_g_monster_mage_attack_grenade_damage, autocvar_g_monster_mage_attack_grenade_edgedamage, autocvar_g_monster_mage_attack_grenade_radius, world, autocvar_g_monster_mage_attack_grenade_force, DEATH_MONSTER_MAGE, other);
+       remove(self);
+}
+
+void mage_grenade_touch()
+{
+       if(IS_PLAYER(other))
+       {
+               PROJECTILE_TOUCH;
+               mage_grenade_explode();
+               return;
+       }
+}
+
+void mage_throw_itemgrenade()
+{
+       makevectors(self.angles);
+
+       W_SetupShot_ProjectileSize (self, '-64 -64 -64', '64 64 64', FALSE, 4, "", CH_WEAPON_A, autocvar_g_monster_mage_attack_grenade_damage);
+       w_shotdir = v_forward; // no TrueAim for grenades please
+
+       entity gren = spawn ();
+       gren.owner = gren.realowner = self;
+       gren.classname = "grenade";
+       gren.bot_dodge = FALSE;
+       gren.movetype = MOVETYPE_BOUNCE;
+       gren.solid = SOLID_TRIGGER;
+       gren.projectiledeathtype = DEATH_MONSTER_MAGE;
+       setorigin(gren, w_shotorg);
+       setsize(gren, '-64 -64 -64', '64 64 64');
+
+       gren.nextthink = time + autocvar_g_monster_mage_attack_grenade_lifetime;
+       gren.think = mage_grenade_explode;
+       gren.use = mage_grenade_explode;
+       gren.touch = mage_grenade_touch;
+
+       gren.missile_flags = MIF_SPLASH | MIF_ARC;
+       W_SETUPPROJECTILEVELOCITY_UP(gren, g_monster_mage_attack_grenade);
+       
+       gren.flags = FL_PROJECTILE;
+       
+       setmodel(gren, "models/items/g_h50.md3");
+       
+       self.attack_finished_single = time + 1.5;
+}
+
+void mage_spike_explode()
+{
+       self.event_damage = func_null;
+
+       pointparticles(particleeffectnum("explosion_small"), self.origin, '0 0 0', 1);
+       RadiusDamage (self, self.realowner, autocvar_g_monster_mage_attack_spike_damage, autocvar_g_monster_mage_attack_spike_damage * 0.5, autocvar_g_monster_mage_attack_spike_radius, world, 0, DEATH_MONSTER_MAGE, other);
+
+       remove (self);
+}
+
+void mage_spike_touch()
+{
+       PROJECTILE_TOUCH;
+
+       mage_spike_explode();
+}
+
+void mage_spike_think()
+{
+       if(self.enemy.health <= 0 || self.owner.health <= 0 || time >= self.ltime)
+       {
+               mage_spike_explode();
+               return;
+       }
+       
+       vector dir = normalize((self.enemy.origin + '0 0 10') - self.origin);
+       
+       UpdateCSQCProjectile(self);
+       
+       if (monster_skill == 3)
+               self.velocity = dir * 350;
+       else
+               self.velocity = dir * 250;
+               
+       self.nextthink = time + 0.2;
+       self.think = mage_spike_think;  
+}
+
+void mage_spike()
+{
+       entity missile;
+       vector dir = normalize((self.enemy.origin + '0 0 10') - self.origin);
+
+       makevectors(self.angles);
+
+       missile = spawn ();
+       missile.owner = missile.realowner = self;
+       missile.think = mage_spike_think;
+       missile.ltime = time + 7;
+       missile.nextthink = time;
+       missile.solid = SOLID_BBOX;
+       missile.movetype = MOVETYPE_FLYMISSILE;
+       missile.flags = FL_PROJECTILE;
+       setorigin(missile, self.origin + v_forward * 14 + '0 0 30' + v_right * -14);
+       setsize (missile, '0 0 0', '0 0 0');    
+       missile.velocity = dir * 400;
+       missile.avelocity = '300 300 300';
+       missile.enemy = self.enemy;
+       missile.touch = mage_spike_touch;
+       
+       CSQCProjectile(missile, TRUE, PROJECTILE_MAGE_SPIKE, TRUE);
+}
+
+void mage_heal()
+{
+       entity head;
+       float washealed = FALSE;
+       
+       for(head = world; (head = findfloat(head, monster_attack, TRUE)); ) if(friend_needshelp(head))
+       {
+               washealed = TRUE;
+               string fx = "";
+               if(IS_PLAYER(head))
+               {
+                       switch(self.skin)
+                       {
+                               case 0:
+                                       if(head.health < autocvar_g_balance_health_regenstable) head.health = bound(0, head.health + autocvar_g_monster_mage_heal_friends, autocvar_g_balance_health_regenstable);
+                                       fx = "healing_fx";
+                                       break;
+                               case 1:
+                                       if(head.ammo_cells) head.ammo_cells = bound(head.ammo_cells, head.ammo_cells + 1, g_pickup_cells_max);
+                                       if(head.ammo_rockets) head.ammo_rockets = bound(head.ammo_rockets, head.ammo_rockets + 1, g_pickup_rockets_max);
+                                       if(head.ammo_shells) head.ammo_shells = bound(head.ammo_shells, head.ammo_shells + 2, g_pickup_shells_max);
+                                       if(head.ammo_nails) head.ammo_nails = bound(head.ammo_nails, head.ammo_nails + 5, g_pickup_nails_max);
+                                       fx = "ammoregen_fx";
+                                       break;
+                               case 2:
+                                       if(head.armorvalue < autocvar_g_balance_armor_regenstable)
+                                       {
+                                               head.armorvalue = bound(0, head.armorvalue + autocvar_g_monster_mage_heal_friends, autocvar_g_balance_armor_regenstable);
+                                               fx = "armorrepair_fx";
+                                       }
+                                       break;
+                               case 3:
+                                       head.health = bound(0, head.health - ((head == self)  ? autocvar_g_monster_mage_heal_self : autocvar_g_monster_mage_heal_friends), autocvar_g_balance_health_regenstable);
+                                       fx = "rage";
+                                       break;
+                       }
+                       
+                       pointparticles(particleeffectnum(fx), head.origin, '0 0 0', 1);
+               }
+               else
+               {
+                       pointparticles(particleeffectnum("healing_fx"), head.origin, '0 0 0', 1);
+                       head.health = bound(0, head.health + autocvar_g_monster_mage_heal_friends, head.max_health);
+                       WaypointSprite_UpdateHealth(head.sprite, head.health);
+               }
+       }
+       
+       if(washealed)
+       {
+               monsters_setframe(mage_anim_attack);
+               self.attack_finished_single = time + autocvar_g_monster_mage_heal_delay;
+       }
+}
+
+void mage_shield_die()
+{
+       if not(self.weaponentity)
+               return; // why would this be called without a shield?
+       
+       self.armorvalue = 1;
+       
+       remove(self.weaponentity);
+       
+       self.weaponentity = world;
+}
+
+void mage_shield()
+{
+       if(self.weaponentity)
+               return; // already have a shield
+               
+       entity shield = spawn();
+
+       shield.owner = self;
+       shield.team = self.team;
+       shield.ltime = time + autocvar_g_monster_mage_shield_time;
+       shield.health = 70;
+       shield.classname = "shield";
+       shield.effects = EF_ADDITIVE;
+       shield.movetype = MOVETYPE_NOCLIP;
+       shield.solid = SOLID_TRIGGER;
+       shield.avelocity = '7 0 11';
+       shield.scale = self.scale * 0.6;
+       
+       setattachment(shield, self, "");
+       setmodel(shield, "models/ctf/shield.md3");
+       setsize(shield, shield.scale * shield.mins, shield.scale * shield.maxs);
+       
+       self.weaponentity = shield;
+       
+       self.lastshielded = time + autocvar_g_monster_mage_shield_delay;
+       
+       monsters_setframe(mage_anim_attack);
+       self.attack_finished_single = time + 1;
+       
+       self.armorvalue = autocvar_g_monster_mage_shield_blockpercent / 100;
+}
+
+float mage_attack(float attack_type)
+{
+       switch(attack_type)
+       {
+               case MONSTER_ATTACK_MELEE:
+               {
+                       monsters_setframe(mage_anim_attack);
+                       self.attack_finished_single = time + autocvar_g_monster_mage_attack_melee_delay;
+                       defer(0.2, mageattack_melee);
+                       
+                       return TRUE;
+               }
+               case MONSTER_ATTACK_RANGED:
+               {
+                       if(random() < autocvar_g_monster_mage_attack_grenade_chance / 100)
+                       {
+                               mage_throw_itemgrenade();
+                               return TRUE;
+                       }
+       
+                       monsters_setframe(mage_anim_attack);
+                       self.attack_finished_single = time + autocvar_g_monster_mage_attack_spike_delay;
+                       defer(0.2, mage_spike);
+                       
+                       return TRUE;
+               }
+       }
+       
+       return FALSE;
+}
+
+void spawnfunc_monster_mage()
+{
+       if not(autocvar_g_monster_mage) { remove(self); return; }
+       
+       self.classname = "monster_mage";
+       
+       self.monster_spawnfunc = spawnfunc_monster_mage;
+       
+       if(Monster_CheckAppearFlags(self))
+               return;
+       
+       if not(monster_initialize(MON_MAGE, FALSE)) { remove(self); return; }
+}
+
+// compatibility with old spawns
+void spawnfunc_monster_shalrath() { spawnfunc_monster_mage(); }
+
+float m_mage(float req)
+{
+       switch(req)
+       {
+               case MR_THINK:
+               {
+                       entity head;
+                       float need_help = FALSE;
+                       
+                       FOR_EACH_PLAYER(head)
+                       if(friend_needshelp(head))
+                       {
+                               need_help = TRUE;
+                               break; // found 1 player near us who is low on health
+                       }
+                       if(!need_help)
+                       FOR_EACH_MONSTER(head)
+                       if(head != self)
+                       if(friend_needshelp(head))
+                       {
+                               need_help = TRUE;
+                               break; // found 1 player near us who is low on health
+                       }
+                       
+                       if(self.weaponentity)
+                       if(time >= self.weaponentity.ltime)
+                               mage_shield_die();
+                               
+                       if(self.health < autocvar_g_monster_mage_heal_minhealth || need_help)
+                       if(time >= self.attack_finished_single)
+                       if(random() < 0.5)
+                               mage_heal();
+                               
+                       if(self.enemy)
+                       if(self.health < self.max_health)
+                       if(time >= self.lastshielded)
+                       if(random() < 0.5)
+                               mage_shield();
+                       
+                       monster_move(autocvar_g_monster_mage_speed, autocvar_g_monster_mage_speed, 50, mage_anim_walk, mage_anim_run, mage_anim_idle);
+                       return TRUE;
+               }
+               case MR_DEATH:
+               {
+                       monsters_setframe(mage_anim_death);
+                       return TRUE;
+               }
+               case MR_SETUP:
+               {
+                       if not(self.health) self.health = autocvar_g_monster_mage_health;
+                               
+                       self.monster_attackfunc = mage_attack;
+                       monsters_setframe(mage_anim_walk);
+                       
+                       return TRUE;
+               }
+               case MR_INIT:
+               {
+                       // nothing
+                       return TRUE;
+               }
+       }
+       
+       return TRUE;
+}
+
+#endif // SVQC
+#ifdef CSQC
+float m_mage(float req)
+{
+       switch(req)
+       {
+               case MR_DEATH:
+               {
+                       // nothing
+                       return TRUE;
+               }
+               case MR_INIT:
+               {
+                       precache_model ("models/monsters/mage.dpm");
+                       return TRUE;
+               }
+       }
+       
+       return TRUE;
+}
+
+#endif // CSQC
+#endif // REGISTER_MONSTER
diff --git a/qcsrc/common/monsters/monster/shambler.qc b/qcsrc/common/monsters/monster/shambler.qc
new file mode 100644 (file)
index 0000000..54a6ec0
--- /dev/null
@@ -0,0 +1,175 @@
+#ifdef REGISTER_MONSTER
+REGISTER_MONSTER(
+/* MON_##id   */ SHAMBLER,
+/* function   */ m_shambler,
+/* spawnflags */ MONSTER_SIZE_BROKEN,
+/* mins,maxs  */ '-41 -41 -31', '41 41 65',
+/* model      */ "shambler.mdl",
+/* netname    */ "shambler",
+/* fullname   */ _("Shambler")
+);
+
+#else
+#ifdef SVQC
+float autocvar_g_monster_shambler;
+float autocvar_g_monster_shambler_health;
+float autocvar_g_monster_shambler_damage;
+float autocvar_g_monster_shambler_attack_lightning_damage;
+float autocvar_g_monster_shambler_attack_claw_damage;
+float autocvar_g_monster_shambler_speed_walk;
+float autocvar_g_monster_shambler_speed_run;
+
+const float shambler_anim_stand        = 0;
+const float shambler_anim_walk                 = 1;
+const float shambler_anim_run          = 2;
+const float shambler_anim_smash        = 3;
+const float shambler_anim_swingr       = 4;
+const float shambler_anim_swingl       = 5;
+const float shambler_anim_magic        = 6;
+const float shambler_anim_pain                 = 7;
+const float shambler_anim_death        = 8;
+
+void shambler_smash()
+{
+       monster_melee(self.enemy, autocvar_g_monster_shambler_damage, 0.3, DEATH_MONSTER_SHAMBLER_SMASH, TRUE);
+}
+
+void shambler_delayedsmash()
+{
+       monsters_setframe(shambler_anim_smash);
+       defer(0.7, shambler_smash);
+       self.attack_finished_single = time + 1.1;
+}
+
+void shambler_swing()
+{
+       float r = (random() < 0.5);
+       monsters_setframe((r) ? shambler_anim_swingr : shambler_anim_swingl);
+       monster_melee(self.enemy, autocvar_g_monster_shambler_attack_claw_damage, 0.3, DEATH_MONSTER_SHAMBLER_CLAW, TRUE);
+       self.attack_finished_single = time + 0.8;
+       if(r)
+               defer(0.5, shambler_swing);
+}
+
+void CastLightning()
+{
+       local vector org, dir;
+       //vector v = '0 0 0';
+
+       self.effects |= EF_MUZZLEFLASH;
+
+       org = self.origin + '0 0 40';
+
+       dir = self.enemy.origin + '0 0 16' - org;
+       dir = normalize (dir);
+
+       traceline (org, self.origin + dir * 1000, TRUE, self);
+               
+       FireRailgunBullet (org, org + dir * 1000, autocvar_g_monster_shambler_attack_lightning_damage * monster_skill, 0, 0, 0, 0, 0, DEATH_MONSTER_SHAMBLER_ZAP);
+       
+       // teamcolor / hit beam effect
+       //v = WarpZone_UnTransformOrigin(WarpZone_trace_transform, trace_endpos);
+       //WarpZone_TrailParticles(world, particleeffectnum("TE_TEI_G3"), org, v);
+       
+       te_csqc_lightningarc(org, trace_endpos);
+}
+
+float shambler_attack(float attack_type)
+{
+       switch(attack_type)
+       {
+               case MONSTER_ATTACK_MELEE:
+               {
+                       float chance = random();
+
+                       if(chance > 0.6)
+                               shambler_delayedsmash();
+                       else
+                               shambler_swing();
+                       
+                       return TRUE;
+               }
+               case MONSTER_ATTACK_RANGED:
+               {
+                       monsters_setframe(shambler_anim_magic);
+                       self.attack_finished_single = time + 1.1;
+                       defer(0.6, CastLightning);
+                       
+                       return TRUE;
+               }
+       }
+       
+       return FALSE;
+}
+
+void spawnfunc_monster_shambler()
+{
+       if not(autocvar_g_monster_shambler) { remove(self); return; }
+       
+       self.classname = "monster_shambler";
+       
+       self.monster_spawnfunc = spawnfunc_monster_shambler;
+       
+       if(Monster_CheckAppearFlags(self))
+               return;
+       
+       if not(monster_initialize(MON_SHAMBLER, FALSE)) { remove(self); return; }
+}
+
+float m_shambler(float req)
+{
+       switch(req)
+       {
+               case MR_THINK:
+               {
+                       monster_move(autocvar_g_monster_shambler_speed_run, autocvar_g_monster_shambler_speed_walk, 300, shambler_anim_run, shambler_anim_walk, shambler_anim_stand);
+                       return TRUE;
+               }
+               case MR_DEATH:
+               {
+                       monsters_setframe(shambler_anim_death);
+                       return TRUE;
+               }
+               case MR_SETUP:
+               {
+                       if not(self.health) self.health = autocvar_g_monster_shambler_health;
+                               
+                       self.monster_attackfunc = shambler_attack;
+                       monsters_setframe(shambler_anim_stand);
+                       self.weapon = WEP_NEX;
+                       
+                       return TRUE;
+               }
+               case MR_INIT:
+               {
+                       // nothing
+                       return TRUE;
+               }
+       }
+       
+       return TRUE;
+}
+
+#endif // SVQC
+#ifdef CSQC
+float m_shambler(float req)
+{
+       switch(req)
+       {
+               case MR_DEATH:
+               {
+                       // nothing
+                       return TRUE;
+               }
+               case MR_INIT:
+               {
+                       precache_model ("models/monsters/shambler.mdl");
+                       return TRUE;
+               }
+       }
+       
+       return TRUE;
+}
+
+#endif // CSQC
+#endif // REGISTER_MONSTER
diff --git a/qcsrc/common/monsters/monster/slime.qc b/qcsrc/common/monsters/monster/slime.qc
new file mode 100644 (file)
index 0000000..5e206f2
--- /dev/null
@@ -0,0 +1,163 @@
+#ifdef REGISTER_MONSTER
+REGISTER_MONSTER(
+/* MON_##id   */ SLIME,
+/* function   */ m_slime,
+/* spawnflags */ 0,
+/* mins,maxs  */ '-16 -16 -24', '16 16 16',
+/* model      */ "slime.dpm",
+/* netname    */ "slime",
+/* fullname   */ _("Slime")
+);
+
+#else
+#ifdef SVQC
+float autocvar_g_monster_slime;
+float autocvar_g_monster_slime_health;
+float autocvar_g_monster_slime_speed_walk;
+float autocvar_g_monster_slime_speed_run;
+
+const float slime_anim_walk            = 0;
+const float slime_anim_idle            = 1;
+const float slime_anim_jump            = 2;
+const float slime_anim_fly             = 3;
+const float slime_anim_die             = 4;
+const float slime_anim_pain            = 5;
+
+void slime_touch_jump()
+{
+       if(self.health > 0)
+       if(other.health > 0)
+       if(other.takedamage)
+       if(vlen(self.velocity) > 200)
+       {
+               Damage (self, world, world, self.health + self.max_health + 200, DEATH_MONSTER_SLIME, self.origin, '0 0 0');
+                       
+               return;
+       }
+
+       if(trace_dphitcontents)
+       {
+               self.touch = MonsterTouch;
+               self.movetype = MOVETYPE_WALK;
+       }
+}
+
+float slime_attack(float attack_type)
+{
+       switch(attack_type)
+       {
+               case MONSTER_ATTACK_MELEE:
+               case MONSTER_ATTACK_RANGED:
+               {
+                       makevectors(self.angles);
+                       if(monster_leap(slime_anim_jump, slime_touch_jump, v_forward * 600 + '0 0 200', 0.5))
+                               return TRUE;
+               }
+       }
+       
+       return FALSE;
+}
+
+void slime_explode()
+{
+       RadiusDamage(self, self, 250 * monster_skill, 15, 250 * (monster_skill * 0.7), world, 250, DEATH_MONSTER_SLIME, world);
+       pointparticles(particleeffectnum("explosion_medium"), self.origin, '0 0 0', 1);
+       sound(self, CH_SHOTS, "weapons/rocket_impact.wav", VOL_BASE, ATTN_NORM);
+       
+       setmodel(self, "");
+}
+
+void slime_dead()
+{
+       self.health = -100; // gibbed
+       slime_explode();
+       
+       Monster_CheckDropCvars ("slime"); // TODO: add a special function to drop items after death
+       
+       self.deadflag = DEAD_DEAD;
+       self.think = Monster_Fade;
+       self.nextthink = time + 0.1;
+}
+
+void spawnfunc_monster_slime()
+{
+       if not(autocvar_g_monster_slime) { remove(self); return; }
+       
+       self.classname = "monster_slime";
+       
+       self.monster_spawnfunc = spawnfunc_monster_slime;
+       
+       if(Monster_CheckAppearFlags(self))
+               return;
+       
+       if not(monster_initialize(MON_SLIME, FALSE)) { remove(self); return; }
+}
+
+// compatibility with old spawns
+void spawnfunc_monster_tarbaby() { spawnfunc_monster_slime(); }
+
+float m_slime(float req)
+{
+       switch(req)
+       {
+               case MR_THINK:
+               {
+                       monster_move(autocvar_g_monster_slime_speed_run, autocvar_g_monster_slime_speed_walk, 20, slime_anim_walk, slime_anim_walk, slime_anim_idle);
+                       return TRUE;
+               }
+               case MR_DEATH:
+               {
+                       self.think                      = slime_dead;
+                       self.nextthink          = time;
+                       self.event_damage   = func_null;
+                       self.movetype           = MOVETYPE_NONE;
+                       self.takedamage         = DAMAGE_NO;
+                       self.enemy                      = world;
+                       self.health                     = 0;
+                       
+                       self.SendFlags |= MSF_MOVE | MSF_STATUS;
+                       
+                       return TRUE;
+               }
+               case MR_SETUP:
+               {
+                       if not(self.health) self.health = autocvar_g_monster_slime_health;
+                               
+                       self.monster_attackfunc = slime_attack;
+                       monsters_setframe(slime_anim_idle);
+                       
+                       return TRUE;
+               }
+               case MR_INIT:
+               {
+                       // nothing
+                       return TRUE;
+               }
+       }
+       
+       return TRUE;
+}
+
+#endif // SVQC
+#ifdef CSQC
+float m_slime(float req)
+{
+       switch(req)
+       {
+               case MR_DEATH:
+               {
+                       // nothing
+                       return TRUE;
+               }
+               case MR_INIT:
+               {
+                       precache_model ("models/monsters/slime.dpm");
+                       return TRUE;
+               }
+       }
+       
+       return TRUE;
+}
+
+#endif // CSQC
+#endif // REGISTER_MONSTER
diff --git a/qcsrc/common/monsters/monster/spider.qc b/qcsrc/common/monsters/monster/spider.qc
new file mode 100644 (file)
index 0000000..f98ee8f
--- /dev/null
@@ -0,0 +1,234 @@
+#ifdef REGISTER_MONSTER
+REGISTER_MONSTER(
+/* MON_##id   */ SPIDER,
+/* function   */ m_spider,
+/* spawnflags */ 0,
+/* mins,maxs  */ '-18 -18 -25', '18 18 30',
+/* model      */ "spider.dpm",
+/* netname    */ "spider",
+/* fullname   */ _("Spider")
+);
+
+#else
+#ifdef SVQC
+float autocvar_g_monster_spider;
+float autocvar_g_monster_spider_stopspeed;
+float autocvar_g_monster_spider_attack_leap_delay;
+float autocvar_g_monster_spider_attack_stand_damage;
+float autocvar_g_monster_spider_attack_stand_delay;
+float autocvar_g_monster_spider_attack_fire_time;
+float autocvar_g_monster_spider_health;
+float autocvar_g_monster_spider_speed_walk;
+float autocvar_g_monster_spider_speed_run;
+float autocvar_g_monster_spider_attack_type;
+
+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_type; // used to switch between fire & ice attacks
+const float SPIDER_TYPE_ICE            = 0;
+const float SPIDER_TYPE_FIRE   = 1;
+
+void spider_web_explode()
+{
+       entity e;
+       if(self)
+       {
+               float damg = 0, edamg = 0, rad = 1;
+               switch(self.realowner.spider_type)
+               {
+                       case SPIDER_TYPE_ICE:
+                               rad = 25;
+                               pointparticles(particleeffectnum("electro_impact"), self.origin, '0 0 0', 1);
+                               break;
+                       case SPIDER_TYPE_FIRE:
+                               pointparticles(particleeffectnum("fireball_explode"), self.origin, '0 0 0', 1);
+                               damg = 15;
+                               rad = 25;
+                               edamg = 6;
+                               break;
+               }
+               
+               RadiusDamage(self, self.realowner, damg, edamg, 0, world, rad, DEATH_MONSTER_SPIDER_FIRE, world); // ice deals no damage anyway
+               
+               for(e = findradius(self.origin, rad); e; e = e.chain) if(e.takedamage && e.deadflag == DEAD_NO) if(e.health > 0)
+               {
+                       switch(self.realowner.spider_type)
+                       {
+                               case SPIDER_TYPE_ICE:
+                                       Freeze(e, 0.3, 2, FALSE);
+                                       break;
+                               case SPIDER_TYPE_FIRE:
+                                       Fire_AddDamage(e, self.realowner, 5 * monster_skill, autocvar_g_monster_spider_attack_fire_time, DEATH_MONSTER_SPIDER_FIRE);
+                                       break;
+                       }
+               }
+               
+               remove(self);
+       }
+}
+
+void spider_web_touch()
+{
+       PROJECTILE_TOUCH;
+       
+       spider_web_explode();
+}
+
+void spider_shootweb(float ptype)
+{
+       float p = 0;
+       string snd = "";
+       switch(ptype)
+       {
+               case SPIDER_TYPE_ICE:
+                       p = PROJECTILE_ELECTRO;
+                       snd = "weapons/electro_fire2.wav";
+                       break;
+               case SPIDER_TYPE_FIRE:
+                       p = PROJECTILE_FIREMINE;
+                       snd = "weapons/fireball_fire.wav";
+                       break;
+       }
+       
+       vector fmins = '-4 -4 -4', fmaxs = '4 4 4';
+
+       W_SetupShot_ProjectileSize(self, fmins, fmaxs, FALSE, 2, snd, CH_WEAPON_A, 0);
+
+       w_shotdir = v_forward; // no TrueAim for grenades please
+
+       entity proj = spawn ();
+       proj.classname = "plasma";
+       proj.owner = proj.realowner = self;
+       proj.use = spider_web_touch;
+       proj.think = adaptor_think2use_hittype_splash;
+       proj.bot_dodge = TRUE;
+       proj.bot_dodgerating = 0;
+       proj.nextthink = time + 5;
+       PROJECTILE_MAKETRIGGER(proj);
+       proj.projectiledeathtype = DEATH_MONSTER_SPIDER_FIRE;
+       setorigin(proj, w_shotorg);
+
+       //proj.glow_size = 50;
+       //proj.glow_color = 45;
+       proj.movetype = MOVETYPE_BOUNCE;
+       W_SETUPPROJECTILEVELOCITY_UP(proj, g_monster_spider_attack_web);
+       proj.touch = spider_web_touch;
+       setsize(proj, fmins, fmaxs);
+       proj.takedamage = DAMAGE_NO;
+       proj.damageforcescale = 0;
+       proj.health = 500;
+       proj.event_damage = func_null;
+       proj.flags = FL_PROJECTILE;
+       proj.damagedbycontents = TRUE;
+
+       proj.bouncefactor = 0.3;
+       proj.bouncestop = 0.05;
+       proj.missile_flags = MIF_SPLASH | MIF_ARC;
+
+       CSQCProjectile(proj, TRUE, p, TRUE);
+}
+
+float spider_attack(float attack_type)
+{
+       switch(attack_type)
+       {
+               case MONSTER_ATTACK_MELEE:
+               {
+                       monster_melee(self.enemy, autocvar_g_monster_spider_attack_stand_damage, 0.3, DEATH_MONSTER_SPIDER, TRUE);
+                       monsters_setframe((random() > 0.5) ? spider_anim_attack : spider_anim_attack2);
+                       self.attack_finished_single = time + autocvar_g_monster_spider_attack_stand_delay;
+                       
+                       return TRUE;
+               }
+               case MONSTER_ATTACK_RANGED:
+               {
+                       if(self.enemy.frozen)
+                               return FALSE;
+                       
+                       monsters_setframe(spider_anim_attack2);
+                       self.attack_finished_single = time + autocvar_g_monster_spider_attack_leap_delay;
+                       monster_makevectors(self.enemy);
+                       spider_shootweb(self.spider_type);
+                       
+                       return TRUE;
+               }
+       }
+       
+       return FALSE;
+}
+
+void spawnfunc_monster_spider() 
+{
+       if not(autocvar_g_monster_spider) { remove(self); return; }
+       
+       self.classname = "monster_spider";
+       
+       self.monster_spawnfunc = spawnfunc_monster_spider;
+       
+       if(Monster_CheckAppearFlags(self))
+               return;
+       
+       if not(monster_initialize(MON_SPIDER, FALSE)) { remove(self); return; }
+}
+
+float m_spider(float req)
+{
+       switch(req)
+       {
+               case MR_THINK:
+               {
+                       monster_move(autocvar_g_monster_spider_speed_run, autocvar_g_monster_spider_speed_walk, autocvar_g_monster_spider_stopspeed, spider_anim_walk, spider_anim_walk, spider_anim_idle);
+                       return TRUE;
+               }
+               case MR_DEATH:
+               {
+                       monsters_setframe(spider_anim_attack);
+                       self.angles += '180 0 0';
+                       return TRUE;
+               }
+               case MR_SETUP:
+               {
+                       if not(self.health) self.health = autocvar_g_monster_spider_health;
+                       if not(self.spider_type) self.spider_type = autocvar_g_monster_spider_attack_type;
+                               
+                       self.monster_attackfunc = spider_attack;
+                       monsters_setframe(spider_anim_idle);
+                       
+                       return TRUE;
+               }
+               case MR_INIT:
+               {
+                       // nothing
+                       return TRUE;
+               }
+       }
+       
+       return TRUE;
+}
+
+#endif // SVQC
+#ifdef CSQC
+float m_spider(float req)
+{
+       switch(req)
+       {
+               case MR_DEATH:
+               {
+                       self.angles += '180 0 0'; // TODO: use the server side angles instead?
+                       return TRUE;
+               }
+               case MR_INIT:
+               {
+                       precache_model ("models/monsters/spider.dpm");
+                       return TRUE;
+               }
+       }
+       
+       return TRUE;
+}
+
+#endif // CSQC
+#endif // REGISTER_MONSTER
diff --git a/qcsrc/common/monsters/monster/stingray.qc b/qcsrc/common/monsters/monster/stingray.qc
new file mode 100644 (file)
index 0000000..08c7635
--- /dev/null
@@ -0,0 +1,112 @@
+#ifdef REGISTER_MONSTER
+REGISTER_MONSTER(
+/* MON_##id   */ STINGRAY,
+/* function   */ m_stingray,
+/* spawnflags */ MONSTER_TYPE_SWIM | MONSTER_SIZE_BROKEN,
+/* mins,maxs  */ '-20 -20 -31', '20 20 20',
+/* model      */ "fish.mdl",
+/* netname    */ "stingray",
+/* fullname   */ _("Stingray")
+);
+
+#else
+#ifdef SVQC
+float autocvar_g_monster_stingray;
+float autocvar_g_monster_stingray_health;
+float autocvar_g_monster_stingray_damage;
+float autocvar_g_monster_stingray_speed_walk;
+float autocvar_g_monster_stingray_speed_run;
+
+const float stingray_anim_attack = 0;
+const float stingray_anim_death  = 1;
+const float stingray_anim_swim   = 2;
+const float stingray_anim_pain   = 3;
+
+float stingray_attack(float attack_type)
+{
+       switch(attack_type)
+       {
+               case MONSTER_ATTACK_MELEE:
+               {
+                       monsters_setframe(stingray_anim_attack);
+                       self.attack_finished_single = time + 0.5;
+                       monster_melee(self.enemy, autocvar_g_monster_stingray_damage, 0.1, DEATH_MONSTER_STINGRAY, FALSE);
+                       
+                       return TRUE;
+               }
+               case MONSTER_ATTACK_RANGED:
+       }
+       
+       return FALSE;
+}
+
+void spawnfunc_monster_stingray()
+{
+       if not(autocvar_g_monster_stingray) { remove(self); return; }
+       
+       self.classname = "monster_stingray";
+       
+       self.monster_spawnfunc = spawnfunc_monster_stingray;
+       
+       if(Monster_CheckAppearFlags(self))
+               return;
+       
+       if not(monster_initialize(MON_STINGRAY, TRUE)) { remove(self); return; }
+}
+
+float m_stingray(float req)
+{
+       switch(req)
+       {
+               case MR_THINK:
+               {
+                       monster_move(autocvar_g_monster_stingray_speed_run, autocvar_g_monster_stingray_speed_walk, 10, stingray_anim_swim, stingray_anim_swim, stingray_anim_swim);
+                       return TRUE;
+               }
+               case MR_DEATH:
+               {
+                       monsters_setframe(stingray_anim_swim);
+                       return TRUE;
+               }
+               case MR_SETUP:
+               {
+                       if not(self.health) self.health = autocvar_g_monster_stingray_health;
+                               
+                       self.monster_attackfunc = stingray_attack;
+                       monsters_setframe(stingray_anim_death);
+                       
+                       return TRUE;
+               }
+               case MR_INIT:
+               {
+                       // nothing
+                       return TRUE;
+               }
+       }
+       
+       return TRUE;
+}
+
+#endif // SVQC
+#ifdef CSQC
+float m_stingray(float req)
+{
+       switch(req)
+       {
+               case MR_DEATH:
+               {
+                       // nothing
+                       return TRUE;
+               }
+               case MR_INIT:
+               {
+                       precache_model ("models/monsters/fish.mdl");
+                       return TRUE;
+               }
+       }
+       
+       return TRUE;
+}
+
+#endif // CSQC
+#endif // REGISTER_MONSTER
diff --git a/qcsrc/common/monsters/monster/wyvern.qc b/qcsrc/common/monsters/monster/wyvern.qc
new file mode 100644 (file)
index 0000000..71e1236
--- /dev/null
@@ -0,0 +1,170 @@
+#ifdef REGISTER_MONSTER
+REGISTER_MONSTER(
+/* MON_##id   */ WYVERN,
+/* function   */ m_wyvern,
+/* spawnflags */ MONSTER_TYPE_FLY | MONSTER_SIZE_BROKEN,
+/* mins,maxs  */ '-20 -20 -58', '20 20 20',
+/* model      */ "wizard.mdl",
+/* netname    */ "wyvern",
+/* fullname   */ _("Wyvern")
+);
+
+#else
+#ifdef SVQC
+float autocvar_g_monster_wyvern;
+float autocvar_g_monster_wyvern_health;
+float autocvar_g_monster_wyvern_speed_walk;
+float autocvar_g_monster_wyvern_speed_run;
+float autocvar_g_monster_wyvern_fireball_damage;
+float autocvar_g_monster_wyvern_fireball_force;
+float autocvar_g_monster_wyvern_fireball_radius;
+float autocvar_g_monster_wyvern_fireball_edgedamage;
+float autocvar_g_monster_wyvern_fireball_damagetime;
+float autocvar_g_monster_wyvern_fireball_speed;
+
+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()
+{
+       entity e;
+       if(self)
+       {
+               pointparticles(particleeffectnum("fireball_explode"), self.origin, '0 0 0', 1);
+               
+               RadiusDamage(self, self.realowner, autocvar_g_monster_wyvern_fireball_damage, autocvar_g_monster_wyvern_fireball_edgedamage, autocvar_g_monster_wyvern_fireball_force, world, autocvar_g_monster_wyvern_fireball_radius, self.projectiledeathtype, world);
+               
+               for(e = world; (e = findfloat(e, takedamage, DAMAGE_AIM)); ) if(vlen(e.origin - self.origin) <= autocvar_g_monster_wyvern_fireball_radius)
+                       Fire_AddDamage(e, self, 5 * monster_skill, autocvar_g_monster_wyvern_fireball_damagetime, self.projectiledeathtype);
+               
+               remove(self);
+       }
+}
+
+void wyvern_fireball_touch()
+{
+       PROJECTILE_TOUCH;
+       
+       wyvern_fireball_explode();
+}
+
+void wyvern_fireball()
+{
+       entity missile = spawn();
+       vector dir = normalize((self.enemy.origin + '0 0 10') - self.origin);
+       
+       monster_makevectors(self.enemy);
+
+       missile.owner = missile.realowner = self;
+       missile.solid = SOLID_TRIGGER;
+       missile.movetype = MOVETYPE_FLYMISSILE;
+       missile.projectiledeathtype = DEATH_MONSTER_WYVERN;
+       setsize(missile, '-6 -6 -6', '6 6 6');          
+       setorigin(missile, self.origin + self.view_ofs + v_forward * 14);
+       missile.flags = FL_PROJECTILE;
+       missile.velocity = dir * autocvar_g_monster_wyvern_fireball_speed;
+       missile.avelocity = '300 300 300';
+       missile.nextthink = time + 5;
+       missile.think = wyvern_fireball_explode;
+       missile.enemy = self.enemy;
+       missile.touch = wyvern_fireball_touch;
+       CSQCProjectile(missile, TRUE, PROJECTILE_FIREMINE, TRUE);
+}
+
+float wyvern_attack(float attack_type)
+{
+       switch(attack_type)
+       {
+               case MONSTER_ATTACK_MELEE:
+               case MONSTER_ATTACK_RANGED:
+               {
+                       self.attack_finished_single = time + 1.2;
+                       
+                       wyvern_fireball();
+                       
+                       return TRUE;
+               }
+       }
+       
+       return FALSE;
+}
+
+void spawnfunc_monster_wyvern()
+{
+       if not(autocvar_g_monster_wyvern) { remove(self); return; }
+       
+       self.classname = "monster_wyvern";
+       
+       self.monster_spawnfunc = spawnfunc_monster_wyvern;
+       
+       if(Monster_CheckAppearFlags(self))
+               return;
+       
+       if not(monster_initialize(MON_WYVERN, TRUE)) { remove(self); return; }
+}
+
+// compatibility with old spawns
+void spawnfunc_monster_wizard() { spawnfunc_monster_wyvern(); }
+
+float m_wyvern(float req)
+{
+       switch(req)
+       {
+               case MR_THINK:
+               {
+                       monster_move(autocvar_g_monster_wyvern_speed_run, autocvar_g_monster_wyvern_speed_walk, 300, wyvern_anim_fly, wyvern_anim_hover, wyvern_anim_hover);
+                       return TRUE;
+               }
+               case MR_DEATH:
+               {
+                       monsters_setframe(wyvern_anim_death);
+                       self.velocity_x = -200 + 400 * random();
+                       self.velocity_y = -200 + 400 * random();
+                       self.velocity_z = 100 + 100 * random();
+                       return TRUE;
+               }
+               case MR_SETUP:
+               {
+                       if not(self.health) self.health = autocvar_g_monster_wyvern_health;
+                               
+                       self.monster_attackfunc = wyvern_attack;
+                       monsters_setframe(wyvern_anim_hover);
+                       
+                       return TRUE;
+               }
+               case MR_INIT:
+               {
+                       // nothing
+                       return TRUE;
+               }
+       }
+       
+       return TRUE;
+}
+
+#endif // SVQC
+#ifdef CSQC
+float m_wyvern(float req)
+{
+       switch(req)
+       {
+               case MR_DEATH:
+               {
+                       // nothing
+                       return TRUE;
+               }
+               case MR_INIT:
+               {
+                       precache_model ("models/monsters/knight.mdl");
+                       return TRUE;
+               }
+       }
+       
+       return TRUE;
+}
+
+#endif // CSQC
+#endif // REGISTER_MONSTER
diff --git a/qcsrc/common/monsters/monster/zombie.qc b/qcsrc/common/monsters/monster/zombie.qc
new file mode 100644 (file)
index 0000000..9ddfbde
--- /dev/null
@@ -0,0 +1,183 @@
+#ifdef REGISTER_MONSTER
+REGISTER_MONSTER(
+/* MON_##id   */ ZOMBIE,
+/* function   */ m_zombie,
+/* spawnflags */ MONSTER_RESPAWN_DEATHPOINT,
+/* mins,maxs  */ '-18 -18 -25', '18 18 47',
+/* model      */ "zombie.dpm",
+/* netname    */ "zombie",
+/* fullname   */ _("Zombie")
+);
+
+#else
+#ifdef SVQC
+float autocvar_g_monster_zombie;
+float autocvar_g_monster_zombie_stopspeed;
+float autocvar_g_monster_zombie_attack_leap_damage;
+float autocvar_g_monster_zombie_attack_leap_delay;
+float autocvar_g_monster_zombie_attack_leap_force;
+float autocvar_g_monster_zombie_attack_leap_speed;
+float autocvar_g_monster_zombie_attack_stand_damage;
+float autocvar_g_monster_zombie_attack_stand_delay;
+float autocvar_g_monster_zombie_health;
+float autocvar_g_monster_zombie_speed_walk;
+float autocvar_g_monster_zombie_speed_run;
+
+const float zombie_anim_attackleap                     = 0;
+const float zombie_anim_attackrun1                     = 1;
+const float zombie_anim_attackrun2                     = 2;
+const float zombie_anim_attackrun3                     = 3;
+const float zombie_anim_attackstanding1                = 4;
+const float zombie_anim_attackstanding2                = 5;
+const float zombie_anim_attackstanding3                = 6;
+const float zombie_anim_blockend                       = 7;
+const float zombie_anim_blockstart                     = 8;
+const float zombie_anim_deathback1                     = 9;
+const float zombie_anim_deathback2                     = 10;
+const float zombie_anim_deathback3                     = 11;
+const float zombie_anim_deathfront1                    = 12;
+const float zombie_anim_deathfront2                    = 13;
+const float zombie_anim_deathfront3                    = 14;
+const float zombie_anim_deathleft1                     = 15;
+const float zombie_anim_deathleft2                     = 16;
+const float zombie_anim_deathright1                    = 17;
+const float zombie_anim_deathright2                    = 18;
+const float zombie_anim_idle                           = 19;
+const float zombie_anim_painback1                      = 20;
+const float zombie_anim_painback2                      = 21;
+const float zombie_anim_painfront1                     = 22;
+const float zombie_anim_painfront2                     = 23;
+const float zombie_anim_runbackwards           = 24;
+const float zombie_anim_runbackwardsleft       = 25;
+const float zombie_anim_runbackwardsright      = 26;
+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()
+{
+       if (self.health <= 0)
+               return;
+               
+       vector angles_face;
+
+       if(other.takedamage)
+       {
+               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_skill, DEATH_MONSTER_ZOMBIE_JUMP, other.origin, angles_face);
+               self.touch = MonsterTouch; // instantly turn it off to stop damage spam
+       }
+
+       if (trace_dphitcontents)
+               self.touch = MonsterTouch;
+}
+
+float zombie_attack(float attack_type)
+{
+       switch(attack_type)
+       {
+               case MONSTER_ATTACK_MELEE:
+               {
+                       float rand = random(), chosen_anim;
+               
+                       if(rand < 0.33)
+                               chosen_anim = zombie_anim_attackstanding1;
+                       else if(rand < 0.66)
+                               chosen_anim = zombie_anim_attackstanding2;
+                       else
+                               chosen_anim = zombie_anim_attackstanding3;
+                               
+                       monsters_setframe(chosen_anim);
+
+                       self.attack_finished_single = time + autocvar_g_monster_zombie_attack_stand_delay;
+                       
+                       monster_melee(self.enemy, autocvar_g_monster_zombie_attack_stand_damage, 0.3, DEATH_MONSTER_ZOMBIE_MELEE, TRUE);
+                       
+                       return TRUE;
+               }
+               case MONSTER_ATTACK_RANGED:
+               {
+                       makevectors(self.angles);
+                       if(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 TRUE;
+               }
+       }
+       
+       return FALSE;
+}
+
+void spawnfunc_monster_zombie() 
+{
+       if not(autocvar_g_monster_zombie) { remove(self); return; }
+       
+       self.classname = "monster_zombie";
+       
+       self.monster_spawnfunc = spawnfunc_monster_zombie;
+       
+       if(Monster_CheckAppearFlags(self))
+               return;
+       
+       if not(monster_initialize(MON_ZOMBIE, FALSE)) { remove(self); return; }
+}
+
+float m_zombie(float req)
+{
+       switch(req)
+       {
+               case MR_THINK:
+               {
+                       monster_move(autocvar_g_monster_zombie_speed_run, autocvar_g_monster_zombie_speed_walk, autocvar_g_monster_zombie_stopspeed, zombie_anim_runforward, zombie_anim_runforward, zombie_anim_idle);
+                       return TRUE;
+               }
+               case MR_DEATH:
+               {
+                       monsters_setframe((random() > 0.5) ? zombie_anim_deathback1 : zombie_anim_deathfront1);
+                       return TRUE;
+               }
+               case MR_SETUP:
+               {
+                       if not(self.health) self.health = autocvar_g_monster_zombie_health;
+                               
+                       self.monster_attackfunc = zombie_attack;
+                       monsters_setframe(zombie_anim_spawn);
+                       self.spawn_time = time + 2.1;
+                       self.spawnshieldtime = self.spawn_time;
+                       self.respawntime = 0.1;
+                       
+                       return TRUE;
+               }
+               case MR_INIT:
+               {
+                       // nothing
+                       return TRUE;
+               }
+       }
+       
+       return TRUE;
+}
+
+#endif // SVQC
+#ifdef CSQC
+float m_zombie(float req)
+{
+       switch(req)
+       {
+               case MR_DEATH:
+               {
+                       // nothing
+                       return TRUE;
+               }
+               case MR_INIT:
+               {
+                       precache_model ("models/monsters/zombie.dpm");
+                       return TRUE;
+               }
+       }
+       
+       return TRUE;
+}
+
+#endif // CSQC
+#endif // REGISTER_MONSTER
diff --git a/qcsrc/common/monsters/monsters.qc b/qcsrc/common/monsters/monsters.qc
new file mode 100644 (file)
index 0000000..706bf55
--- /dev/null
@@ -0,0 +1,54 @@
+#ifdef SVQC
+#include "lib/defs.qh"
+#include "lib/monsters.qc"
+#include "lib/spawn.qc"
+#endif
+
+#include "all.qh"
+
+// MONSTER PLUGIN SYSTEM
+entity monster_info[MON_MAXCOUNT];
+entity dummy_monster_info;
+
+void register_monster(float id, float(float) func, float monstertype, vector min_s, vector max_s, string modelname, string shortname, string mname)
+{
+       entity e;
+       monster_info[id - 1] = e = spawn();
+       e.classname = "monster_info";
+       e.monsterid = id;
+       e.netname = shortname;
+       e.monster_name = mname;
+       e.monster_func = func;
+       e.mdl = modelname;
+       e.mins = min_s;
+       e.maxs = max_s;
+       e.model = strzone(strcat("models/monsters/", modelname));
+       e.spawnflags = monstertype;
+       
+       func(MR_INIT);
+}
+float m_null(float dummy) { return 0; }
+void register_monsters_done()
+{
+       dummy_monster_info = spawn();
+       dummy_monster_info.classname = "monster_info";
+       dummy_monster_info.monsterid = 0; // you can recognize dummies by this
+       dummy_monster_info.netname = "";
+       dummy_monster_info.monster_name = "Monster";
+       dummy_monster_info.monster_func = m_null;
+       dummy_monster_info.mdl = "";
+       dummy_monster_info.mins = '-0 -0 -0';
+       dummy_monster_info.maxs = '0 0 0';
+       dummy_monster_info.model = "";
+       dummy_monster_info.spawnflags = 0;
+}
+entity get_monsterinfo(float id)
+{
+       entity m;
+       if(id < MON_FIRST || id > MON_LAST)
+               return dummy_monster_info;
+       m = monster_info[id - 1];
+       if(m)
+               return m;
+       return dummy_monster_info;
+}
diff --git a/qcsrc/common/monsters/monsters.qh b/qcsrc/common/monsters/monsters.qh
new file mode 100644 (file)
index 0000000..5124e45
--- /dev/null
@@ -0,0 +1,87 @@
+// monster requests
+#define MR_SETUP          1 // (SERVER) setup monster data
+#define MR_THINK                 2 // (SERVER) logic to run every frame
+#define MR_DEATH          3 // (BOTH) called when monster dies
+#define MR_INIT           4 // (BOTH) precaches models/sounds used by this monster
+#define MR_CONFIG         5 // (ALL)
+
+// functions:
+entity get_monsterinfo(float id);
+
+// entity properties of monsterinfo:
+.float monsterid; // MON_...
+.string netname; // short name
+.string monster_name; // human readable name
+.float(float) monster_func; // m_...
+.string mdl; // currently a copy of the model
+.string model; // full name of model
+.float spawnflags;
+
+// 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 monstertype, vector min_s, vector max_s, string modelname, string shortname, string mname);
+void register_monsters_done();
+
+// special spawn flags
+const float MONSTER_RESPAWN_DEATHPOINT = 699; // re-spawn where we died
+const float MONSTER_TYPE_FLY = 700;
+const float MONSTER_TYPE_SWIM = 701;
+const float MONSTER_SIZE_BROKEN = 702; // TODO: remove when bad models are replaced
+
+const float MON_MAXCOUNT = 24;
+#define MON_FIRST 1
+float MON_COUNT;
+float MON_LAST;
+
+#define REGISTER_MONSTER_2(id,func,monstertype,min_s,max_s,modelname,shortname,mname) \
+       float id; \
+       float func(float); \
+       void RegisterMonsters_##id() \
+       { \
+               MON_LAST = (id = MON_FIRST + MON_COUNT); \
+               ++MON_COUNT; \
+               register_monster(id,func,monstertype,min_s,max_s,modelname,shortname,mname); \
+       } \
+       ACCUMULATE_FUNCTION(RegisterMonsters, RegisterMonsters_##id)
+#define REGISTER_MONSTER(id,func,monstertype,min_s,max_s,modelname,shortname,mname) \
+       REGISTER_MONSTER_2(MON_##id,func,monstertype,min_s,max_s,modelname,shortname,mname)
+
+#define MON_DUPECHECK(dupecheck,cvar) \
+       #ifndef dupecheck \
+               #define dupecheck \
+               float cvar; \
+       #else \
+               #error DUPLICATE MONSTER CVAR: cvar \
+       #endif
+
+#define MON_ADD_CVAR(monster,name) \
+               MON_DUPECHECK(MON_CVAR_##monster##_##name, autocvar_g_monster_##monster##_##name)
+
+#define MON_CVAR(monster,name) autocvar_g_balance_##monster##_##name
+
+#define MON_ADD_PROP(monster,prop,name) \
+       .float ##prop; \
+       MON_DUPECHECK(MON_CVAR_##monster##_##name, autocvar_g_monster_##monster##_##name)
+
+#define MON_SET_PROP(wepid,monster,prop,name) get_monsterinfo(##wepid).##prop = autocvar_g_monster_##monster##_##name;
+
+#define MON_SET_PROPS(monsettings,wepid) \
+       #define MON_ADD_CVAR(monster,mode,name) \
+       #define MON_ADD_PROP(monster,prop,name) MON_SET_PROP(wepid,monster,prop,name) \
+       monsettings \
+       #undef MON_ADD_CVAR \
+       #undef MON_ADD_PROP
+
+#include "all.qh"
+
+#undef MON_ADD_CVAR
+#undef MON_ADD_PROP
+#undef REGISTER_MONSTER
+ACCUMULATE_FUNCTION(RegisterMonsters, register_monsters_done)
index a12fc3f14b1ea55f5539a2c9a8554e3c9f5b7a1b..ce049adf305e31d93ec3fc8b37ddf651c978440a 100644 (file)
@@ -197,7 +197,7 @@ void ClientCommand_mobedit(float request, float argc)
                        switch(argv(1))
                        {
                                case "name": trace_ent.netname = strzone(strdecolorize(argv(2))); if(trace_ent.sprite) WaypointSprite_UpdateSprites(trace_ent.sprite, trace_ent.netname, "", ""); return;
-                               case "skin": if(trace_ent.monsterid != MONSTER_MAGE) { trace_ent.skin = stof(argv(2)); trace_ent.SendFlags |= MSF_STATUS; } return;
+                               case "skin": if(trace_ent.monsterid != MON_MAGE) { trace_ent.skin = stof(argv(2)); trace_ent.SendFlags |= MSF_STATUS; } return;
                                case "movetarget": trace_ent.monster_moveflags = stof(argv(2)); return;
                        }
                }
@@ -268,8 +268,8 @@ void ClientCommand_mobspawn(float request, float argc)
                                float i;
                                string list = "Available monsters:";
                                
-                               for(i = MONSTER_FIRST + 1; i < MONSTER_LAST; ++i)
-                                       list = strcat(list, " ", monster_id2string(i));
+                               for(i = MON_FIRST; i <= MON_LAST; ++i)
+                                       list = strcat(list, " ", (get_monsterinfo(i)).netname);
                                
                                sprint(self, strcat(list, "\n"));
                                
@@ -295,9 +295,9 @@ void ClientCommand_mobspawn(float request, float argc)
                                //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, moveflag);
-                               if(mname) e.netname = strzone(mname);
+                               if(mname) e.monster_name = strzone(mname);
                                
-                               sprint(self, strcat("Spawned ", e.netname, "\n"));
+                               sprint(self, strcat("Spawned ", e.monster_name, "\n"));
                        }
                        
                        return;
index 34a8a84f588baa79ba112b48b05e47a279c0bc1d..2175ceb42850984524ff52217a4de022a856c6fa 100644 (file)
@@ -146,6 +146,7 @@ void GameCommand_butcher(float 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 montokill, head;
index ecc9c9e50c5607cb9cba2d5eb7d1617db2f8cee5..2e100f2ce10fb25b044d9f6afbcc2e66908d7574 100644 (file)
@@ -734,7 +734,7 @@ void Damage (entity targ, entity inflictor, entity attacker, float damage, float
                        mirrorforce *= g_weaponforcefactor;
                }
                
-               if(((targ.frozen == 2 && attacker.monsterid != MONSTER_SPIDER) || (targ.frozen == 1)) && deathtype != DEATH_HURTTRIGGER)
+               if(((targ.frozen == 2 && attacker.monsterid != MON_SPIDER) || (targ.frozen == 1)) && deathtype != DEATH_HURTTRIGGER)
                {
                        damage = 0;
                        force *= 0.2;
index 23f51f54a4d1b5086642f2608081bf2962514a46..c9e24123a826645aae94f96ab07629df5af42d01 100644 (file)
@@ -546,6 +546,7 @@ void spawnfunc___init_dedicated_server(void)
 
        // needs to be done so early because of the constants they create
        CALL_ACCUMULATED_FUNCTION(RegisterWeapons);
+       CALL_ACCUMULATED_FUNCTION(RegisterMonsters);
        CALL_ACCUMULATED_FUNCTION(RegisterGametypes);
        CALL_ACCUMULATED_FUNCTION(RegisterNotifications);
        CALL_ACCUMULATED_FUNCTION(RegisterDeathtypes);
@@ -594,6 +595,7 @@ void spawnfunc_worldspawn (void)
 
        // needs to be done so early because of the constants they create
        CALL_ACCUMULATED_FUNCTION(RegisterWeapons);
+       CALL_ACCUMULATED_FUNCTION(RegisterMonsters);
        CALL_ACCUMULATED_FUNCTION(RegisterGametypes);
        CALL_ACCUMULATED_FUNCTION(RegisterNotifications);
        CALL_ACCUMULATED_FUNCTION(RegisterDeathtypes);
diff --git a/qcsrc/server/monsters/lib/defs.qh b/qcsrc/server/monsters/lib/defs.qh
deleted file mode 100644 (file)
index f5a75e3..0000000
+++ /dev/null
@@ -1,52 +0,0 @@
-.float(float attack_type) monster_attackfunc;
-const float MONSTER_ATTACK_MELEE       = 1;
-const float MONSTER_ATTACK_RANGED      = 2;
-
-.float candrop;
-
-.float attack_range;
-
-.float spawn_time; // stop monster from moving around right after spawning
-
-.string oldtarget2;
-.float lastshielded;
-
-const float MONSTER_RESPAWN_DEATHPOINT = 8; // re-spawn where we died
-
-.float monster_respawned; // used to make sure we're not recounting respawned monster stats
-
-float monsters_spawned;
-
-const float MONSTERSKILL_NOTEASY = 256; // monster will not spawn on skill <= 2
-const float MONSTERSKILL_NOTMEDIUM = 512; // monster will not spawn on skill 3
-const float MONSTERSKILL_NOTHARD = 1024; // monster will not spawn on skill 4
-const float MONSTERSKILL_NOTINSANE = 2048; // monster will not spawn on skill 5
-const float MONSTERSKILL_NOTNIGHTMARE = 4096; // monster will not spawn on skill >= 6
-
-// new flags
-const float MONSTERFLAG_MINIBOSS = 1;  // monster spawns as mini-boss (also has a chance of naturally becoming one)
-const float MONSTERFLAG_APPEAR = 2; // delay spawn until triggered
-const float MONSTERFLAG_NORESPAWN = 4;
-const float MONSTERFLAG_SPAWNED = 512; // flag for spawned monsters
-
-.float msound_delay; // restricts some monster sounds
-.string msound_idle;
-.string msound_death;
-.string msound_attack_melee;
-.string msound_attack_ranged;
-.string msound_spawn;
-.string msound_sight;
-.string msound_pain;
-
-.void() monster_spawnfunc;
-.void() monster_die;
-
-.float monster_movestate; // used to tell what the monster is currently doing
-const float MONSTER_MOVE_OWNER = 1; // monster will move to owner if in range, or stand still
-const float MONSTER_MOVE_WANDER = 2; // monster will ignore owner & wander around
-const float MONSTER_MOVE_SPAWNLOC = 3; // monster will move to its spawn location when not attacking
-const float MONSTER_MOVE_NOMOVE = 4; // monster simply stands still
-const float MONSTER_MOVE_ENEMY = 5; // used only as a movestate
-
-const float MONSTER_STATE_ATTACK_LEAP = 1;
-const float MONSTER_STATE_ATTACK_MELEE = 2;
diff --git a/qcsrc/server/monsters/lib/monsters.qc b/qcsrc/server/monsters/lib/monsters.qc
deleted file mode 100644 (file)
index 7e01351..0000000
+++ /dev/null
@@ -1,1048 +0,0 @@
-// TODO: clean up this file?
-
-void() spawnfunc_item_minst_cells;
-
-void M_Item_Touch ()
-{
-       if(self && IS_PLAYER(other) && other.deadflag == DEAD_NO)
-       {
-               Item_Touch();
-               self.think = SUB_Remove;
-               self.nextthink = time + 0.1;
-       }
-}
-
-void monster_item_spawn()
-{
-       if(self.monster_loot)
-               self.monster_loot();
-       
-       self.gravity = 1;
-       self.velocity = randomvec() * 175 + '0 0 325';
-       self.touch = M_Item_Touch;
-       
-       SUB_SetFade(self, time + autocvar_g_monsters_drop_time, 1);
-}
-
-void Monster_DropItem (string itype, string itemsize)
-{
-       vector org = self.origin + ((self.mins + self.maxs) * 0.5);
-       entity e = spawn();
-       
-       setorigin(e, org);
-       
-       switch(itype)
-       {
-               case "armor":
-               {
-                       switch(itemsize)
-                       {
-                               case "mega": e.monster_loot = spawnfunc_item_armor_large; break;
-                               case "large": e.monster_loot = spawnfunc_item_armor_big; break;
-                               case "medium": e.monster_loot = spawnfunc_item_armor_medium; break;
-                               case "small": e.monster_loot = spawnfunc_item_armor_small; break;
-                       }
-                       break;
-               }
-               case "health":
-               {
-                       switch(itemsize)
-                       {
-                               case "mega": e.monster_loot = spawnfunc_item_health_mega; break;
-                               case "large": e.monster_loot = spawnfunc_item_health_large; break;
-                               case "medium": e.monster_loot = spawnfunc_item_health_medium; break;
-                               case "small": e.monster_loot = spawnfunc_item_health_small; break;
-                       }
-                       break;
-               }
-               case "ammo":
-               {
-                       switch(itemsize)
-                       {
-                               case "shells": e.monster_loot = spawnfunc_item_shells; break;
-                               case "cells": e.monster_loot = spawnfunc_item_cells; break;
-                               case "rockets": e.monster_loot = spawnfunc_item_rockets; break;
-                               case "bullets":
-                               case "nails": e.monster_loot = spawnfunc_item_bullets; break;
-                       }
-                       break;
-               }
-       }
-       
-       other = e;
-       MUTATOR_CALLHOOK(MonsterDropItem);
-       e = other;
-               
-       e.think = monster_item_spawn;
-       e.nextthink = time + 0.1;
-}
-
-void monsters_setframe(float _frame)
-{
-       if(self.frame == _frame)
-               return;
-               
-       self.anim_start_time = time;
-       self.frame = _frame;
-       self.SendFlags |= MSF_ANIM;
-}
-
-float monster_isvalidtarget (entity targ, entity ent)
-{
-       if(!targ || !ent)
-               return FALSE; // someone doesn't exist
-               
-       if(time < game_starttime)
-               return FALSE; // monsters do nothing before the match has started
-               
-       WarpZone_TraceLine(ent.origin, targ.origin, MOVE_NORMAL, ent);
-       
-       if(vlen(targ.origin - ent.origin) >= ent.target_range)
-               return FALSE; // enemy is too far away
-               
-       if not(targ.vehicle_flags & VHF_ISVEHICLE)
-       if(trace_ent != targ)
-               return FALSE; // we can't see the enemy
-               
-       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 not(targ.vehicle_flags & VHF_ISVEHICLE) // vehicles dont count as alive?
-       if(targ.deadflag != DEAD_NO || ent.deadflag != DEAD_NO || targ.health <= 0 || ent.health <= 0)
-               return FALSE; // enemy/self is dead
-       
-       if(targ.monster_owner == ent || ent.monster_owner == targ)
-               return FALSE; // enemy owns us, or we own them
-       
-       if not(targ.vehicle_flags & VHF_ISVEHICLE)
-       if(targ.flags & FL_NOTARGET)
-               return FALSE; // enemy can't be targeted
-       
-       if not(autocvar_g_monsters_typefrag)
-       if(targ.BUTTON_CHAT)
-               return FALSE; // no typefragging!
-       
-       if not(IsDifferentTeam(targ, ent))
-               return FALSE; // enemy is on our team
-               
-       if(autocvar_g_monsters_target_infront)
-       if(ent.enemy != targ)
-       {
-               float dot;
-
-               makevectors (ent.angles);
-               dot = normalize (targ.origin - ent.origin) * v_forward;
-               
-               if(dot <= 0.3)
-                       return FALSE;
-       }
-       
-       return TRUE;
-}
-
-entity FindTarget (entity ent) 
-{
-       if(MUTATOR_CALLHOOK(MonsterFindTarget)) { return ent.enemy; } // Handled by a mutator
-       entity e;
-       
-       for(e = world; (e = findflags(e, monster_attack, TRUE)); ) 
-       if(monster_isvalidtarget(e, ent))
-               return e;
-
-       return world;
-}
-
-void MonsterTouch ()
-{
-       if(other == world)
-               return;
-               
-       if(self.enemy != other)
-       if not(other.flags & FL_MONSTER)
-       if(monster_isvalidtarget(other, self))
-               self.enemy = other;
-}
-
-void monster_sound(string msound, float sound_delay, float delaytoo)
-{
-       if(delaytoo && time < self.msound_delay)
-               return; // too early
-               
-       if(msound == "")
-               return; // sound doesn't exist
-
-       sound(self, CHAN_AUTO, msound, VOL_BASE, ATTN_NORM);
-
-       self.msound_delay = time + sound_delay;
-}
-
-void monster_precachesounds(entity e)
-{
-       precache_sound(e.msound_idle);
-       precache_sound(e.msound_death);
-       precache_sound(e.msound_attack_melee);
-       precache_sound(e.msound_attack_ranged);
-       precache_sound(e.msound_sight);
-       precache_sound(e.msound_pain);
-}
-
-void monster_setupsounds(string mon)
-{
-       if(self.msound_idle == "") self.msound_idle = strzone(strcat("monsters/", mon, "_idle.wav"));
-       if(self.msound_death == "") self.msound_death = strzone(strcat("monsters/", mon, "_death.wav"));
-       if(self.msound_pain == "") self.msound_pain = strzone(strcat("monsters/", mon, "_pain.wav"));
-       if(self.msound_attack_melee == "") self.msound_attack_melee = strzone(strcat("monsters/", mon, "_melee.wav"));
-       if(self.msound_attack_ranged == "") self.msound_attack_ranged = strzone(strcat("monsters/", mon, "_attack.wav"));
-       if(self.msound_sight == "") self.msound_sight = strzone(strcat("monsters/", mon, "_sight.wav"));
-}
-
-float monster_melee (entity targ, float damg, float er, float deathtype, float dostop)
-{
-       float dot, rdmg = damg * random();
-
-       if (self.health <= 0)
-               return FALSE;
-       if (targ == world)
-               return FALSE;
-               
-       if(dostop)
-       {
-               self.velocity_x = 0;
-               self.velocity_y = 0;
-               self.state = MONSTER_STATE_ATTACK_MELEE;
-               self.SendFlags |= MSF_MOVE;
-       }
-
-       makevectors (self.angles);
-       dot = normalize (targ.origin - self.origin) * v_forward;
-       
-       if(dot > er)
-               Damage(targ, self, self, rdmg * monster_skill, deathtype, targ.origin, normalize(targ.origin - self.origin));
-               
-       return TRUE;
-}
-
-void Monster_CheckDropCvars (string mon)
-{
-       if not(self.candrop)
-               return; // forced off
-       
-       string dropitem;
-       string dropsize;
-       
-       dropitem = cvar_string(strcat("g_monster_", mon, "_drop"));
-       dropsize = cvar_string(strcat("g_monster_", mon, "_drop_size"));
-       
-       if(autocvar_g_monsters_forcedrop)
-               Monster_DropItem(autocvar_g_monsters_drop_type, autocvar_g_monsters_drop_size);
-       else if(dropitem != "")
-               Monster_DropItem(dropitem, dropsize);      
-       else
-               Monster_DropItem("armor", "medium");
-}
-
-void Monster_CheckMinibossFlag ()
-{
-       if(MUTATOR_CALLHOOK(MonsterCheckBossFlag))
-               return;
-               
-       float chance = random() * 100;
-
-       // g_monsters_miniboss_chance cvar or spawnflags 64 causes a monster to be a miniboss
-       if ((self.spawnflags & MONSTERFLAG_MINIBOSS) || (chance < autocvar_g_monsters_miniboss_chance))
-       {
-               self.health += autocvar_g_monsters_miniboss_healthboost;
-               self.flags |= MONSTERFLAG_MINIBOSS;
-               if not(self.weapon)
-                       self.weapon = WEP_NEX;
-       }
-}
-
-float Monster_CanRespawn(entity ent)
-{
-       other = ent;
-       if(MUTATOR_CALLHOOK(MonsterRespawn))
-               return TRUE; // enabled by a mutator
-               
-       if(ent.spawnflags & MONSTERFLAG_NORESPAWN)
-               return FALSE;
-               
-       if not(autocvar_g_monsters_respawn)
-               return FALSE;
-               
-       return TRUE;
-}
-
-void Monster_Fade ()
-{
-       if(Monster_CanRespawn(self))
-       {
-               self.monster_respawned = TRUE;
-               self.think = self.monster_spawnfunc;
-               self.nextthink = time + self.respawntime;
-               self.deadflag = DEAD_RESPAWNING;
-               if(self.spawnflags & MONSTER_RESPAWN_DEATHPOINT)
-               {
-                       self.pos1 = self.origin;
-                       self.pos2 = self.angles;
-               }
-               self.event_damage = func_null;
-               self.takedamage = DAMAGE_NO;
-               setorigin(self, self.pos1);
-               self.angles = self.pos2;
-               self.health = self.max_health; // TODO: check if resetting to max_health is wise here
-               
-               self.SendFlags |= MSF_MOVE;
-               self.SendFlags |= MSF_STATUS;
-               
-               return;
-       }
-       SUB_SetFade(self, time + 3, 1);
-}
-
-float Monster_CanJump (vector vel)
-{
-       if(self.state)
-               return FALSE; // already attacking
-       if not(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;
-               
-       monsters_setframe(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 not(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))
-       {
-               monster_sound(e.msound_attack_melee, 0, FALSE);
-               return;
-       }
-       
-       if(e.monster_attackfunc(MONSTER_ATTACK_RANGED))
-       {
-               monster_sound(e.msound_attack_ranged, 0, FALSE);
-               return;
-       }
-}
-
-void monster_makevectors(entity e)
-{
-       vector v;
-               
-       v = CENTER_OR_VIEWOFS(e);
-       self.v_angle = vectoangles(v - (self.origin + self.view_ofs));
-       self.v_angle_x = -self.v_angle_x;
-       
-       makevectors(self.v_angle);
-}
-
-void monster_use ()
-{
-       if (self.enemy)
-               return;
-       if (self.health <= 0)
-               return;
-
-       if(!monster_isvalidtarget(activator, self))
-               return;
-
-       self.enemy = activator;
-}
-
-float trace_path(vector from, vector to)
-{
-       vector dir = normalize(to - from) * 15, offset = '0 0 0';
-       float trace1 = trace_fraction;
-       
-       offset_x = dir_y;
-       offset_y = -dir_x;
-       traceline (from+offset, to+offset, TRUE, self);
-       
-       traceline(from-offset, to-offset, TRUE, self);
-               
-       return ((trace1 < trace_fraction) ? trace1 : trace_fraction);
-}
-
-.float last_trace;
-.float last_enemycheck; // for checking enemy
-vector monster_pickmovetarget(entity targ)
-{
-       // enemy is always preferred target
-       if(self.enemy)
-       {
-               self.monster_movestate = MONSTER_MOVE_ENEMY;
-               self.last_trace = time + 1.2;
-               return self.enemy.origin;
-       }
-       
-       switch(self.monster_moveflags)
-       {
-               case MONSTER_MOVE_OWNER:
-               {
-                       self.monster_movestate = MONSTER_MOVE_OWNER;
-                       self.last_trace = time + 0.3;
-                       if(self.monster_owner && self.monster_owner.classname != "td_spawnpoint")
-                               return self.monster_owner.origin;
-               }
-               case MONSTER_MOVE_SPAWNLOC:
-               {
-                       self.monster_movestate = MONSTER_MOVE_SPAWNLOC;
-                       self.last_trace = time + 2;
-                       return self.pos1;
-               }
-               case MONSTER_MOVE_NOMOVE:
-               {
-                       self.monster_movestate = MONSTER_MOVE_NOMOVE;
-                       self.last_trace = time + 2;
-                       return self.origin;
-               }
-               default:
-               case MONSTER_MOVE_WANDER:
-               {
-                       vector pos;
-                       self.monster_movestate = MONSTER_MOVE_WANDER;
-                       self.last_trace = time + 2;
-                               
-                       self.angles_y = random() * 500;
-                       makevectors(self.angles);
-                       pos = self.origin + v_forward * 600;
-                       
-                       if(self.flags & FL_FLY || self.flags & FL_SWIM)
-                       {
-                               pos_z = random() * 200;
-                               if(random() >= 0.5)
-                                       pos_z *= -1;
-                       }
-                       
-                       if(targ)
-                       {
-                               self.last_trace = time + 0.5;
-                               pos = targ.origin;
-                       }
-                       
-                       return pos;
-               }
-       }
-}
-
-void monster_move(float runspeed, float walkspeed, float stopspeed, float manim_run, float manim_walk, float manim_idle)
-{
-       fixedmakevectors(self.angles);
-
-       if(self.target2)
-               self.goalentity = find(world, targetname, self.target2);
-               
-       entity targ;
-
-       if(self.frozen)
-       {
-               self.revive_progress = bound(0, self.revive_progress + frametime * self.revive_speed, 1);
-               self.health = max(1, self.max_health * self.revive_progress);
-               
-               if(self.sprite) WaypointSprite_UpdateHealth(self.sprite, self.health);
-                       
-               movelib_beak_simple(stopspeed);
-                       
-               self.velocity = '0 0 0';
-               self.enemy = world;
-               self.nextthink = time + 0.1;
-               
-               if(self.revive_progress >= 1)
-                       Unfreeze(self); // wait for next think before attacking
-                       
-               self.SendFlags |= MSF_MOVE;
-                       
-               return; // no moving while frozen
-       }
-       
-       if(self.flags & FL_SWIM)
-       {
-               if(self.waterlevel < WATERLEVEL_WETFEET)
-               {
-                       if(time >= self.last_trace)
-                       {
-                               self.last_trace = time + 0.4;
-                               
-                               Damage (self, world, world, 2, DEATH_DROWN, self.origin, '0 0 0');
-                               self.angles = '90 90 0';
-                               if(random() < 0.5)
-                               {
-                                       self.velocity_y += random() * 50;
-                                       self.velocity_x -= random() * 50;
-                               }
-                               else
-                               {
-                                       self.velocity_y -= random() * 50;
-                                       self.velocity_x += random() * 50;
-                               }
-                               self.velocity_z += random() * 150;
-                       }
-                               
-                       
-                       self.movetype = MOVETYPE_BOUNCE;
-                       //self.velocity_z = -200;
-                               
-                       self.SendFlags |= MSF_MOVE | MSF_ANG;
-                       
-                       return;
-               }
-               else
-               {
-                       self.angles = '0 0 0';
-                       self.movetype = MOVETYPE_WALK;
-               }
-       }
-       
-       targ = self.goalentity;
-       
-       monster_target = targ;
-       monster_speed_run = runspeed;
-       monster_speed_walk = walkspeed;
-       
-       if(MUTATOR_CALLHOOK(MonsterMove) || gameover || (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)
-                       monsters_setframe(manim_idle);
-               movelib_beak_simple(stopspeed);
-               self.SendFlags |= MSF_MOVE;
-               return;
-       }
-       
-       targ = monster_target;
-       runspeed = monster_speed_run;
-       walkspeed = monster_speed_walk;
-               
-       if(IsDifferentTeam(self.monster_owner, self))
-               self.monster_owner = world;
-               
-       if(time >= self.last_enemycheck)
-       {
-               if not(monster_isvalidtarget(self.enemy, self))
-                       self.enemy = world;
-               self.last_enemycheck = time + 2;
-       }
-               
-       if(self.enemy && self.enemy.health < 1)
-               self.enemy = world; // enough!
-               
-       if not(self.enemy)
-       {
-               self.enemy = FindTarget(self);
-               if(self.enemy)
-                       monster_sound(self.msound_sight, 0, FALSE);
-       }
-       
-       if(self.state == MONSTER_STATE_ATTACK_MELEE && time >= self.attack_finished_single)
-               self.state = 0;
-               
-       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 not(self.enemy)
-               monster_sound(self.msound_idle, 5, TRUE);
-       
-       if(self.state != MONSTER_STATE_ATTACK_LEAP && self.state != MONSTER_STATE_ATTACK_MELEE)
-               self.steerto = steerlib_attract2(self.moveto, 0.5, 500, 0.95);
-       
-       if(self.state == MONSTER_STATE_ATTACK_LEAP && (self.flags & FL_ONGROUND))
-       {
-               self.state = 0;
-               self.touch = MonsterTouch;
-       }
-       
-       //self.steerto = steerlib_attract2(self.moveto, 0.5, 500, 0.95);
-       
-       float turny = 0;
-       vector real_angle = vectoangles(self.steerto) - self.angles;
-       
-       if(self.state != MONSTER_STATE_ATTACK_LEAP && self.state != MONSTER_STATE_ATTACK_MELEE)
-               turny = 20;
-               
-       if(self.flags & FL_SWIM)
-               turny = vlen(self.angles - self.moveto);
-       
-       if(turny)
-       {
-               turny = bound(turny * -1, shortangle_f(real_angle_y, self.angles_y), turny);
-               self.angles_y += turny;
-       }
-       
-       if(self.state == MONSTER_STATE_ATTACK_MELEE)
-               self.moveto = self.origin;
-       else if(self.enemy)
-               self.moveto = self.moveto * 0.9 + ((self.origin + v_forward * 500) + randomvec() * 400) * 0.1;
-       
-       if not(self.flags & FL_FLY || self.flags & FL_SWIM)
-               self.moveto_z = self.origin_z; 
-       
-       float l = vlen(self.moveto - self.origin);
-       float t1 = trace_path(self.origin+'0 0 10', self.moveto+'0 0 10');
-       float t2 = trace_path(self.origin-'0 0 15', self.moveto-'0 0 15'); 
-       
-       if(self.flags & FL_FLY || self.flags & FL_SWIM)
-               v_forward = normalize(self.moveto - self.origin);
-       
-       if(t1*l-t2*l>50 && (t1*l > 100 || t1 > 0.8))
-       if(self.flags & FL_ONGROUND)
-               movelib_jump_simple(100);
-
-       if(vlen(self.origin - self.moveto) > 64)
-       {
-               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);
-               if(time > self.pain_finished)
-               if(time > self.attack_finished_single)
-                       monsters_setframe((self.enemy) ? manim_run : manim_walk);
-       }
-       else
-       {
-               entity e = find(world, targetname, self.target2);
-               if(e.target2)
-                       self.target2 = e.target2;
-               else if(e.target)
-                       self.target2 = e.target;
-               
-               movelib_beak_simple(stopspeed);
-               if(time > self.attack_finished_single)
-               if(time > self.pain_finished)
-               if (vlen(self.velocity) <= 30)
-                       monsters_setframe(manim_idle);
-       }
-       
-       monster_checkattack(self, self.enemy);
-               
-       self.SendFlags |= MSF_ANG;
-       self.SendFlags |= MSF_MOVE;
-}
-
-void monster_dead_think()
-{
-       self.think = monster_dead_think;
-       self.nextthink = time + 0.3; // don't need to update so often now
-       
-       self.deadflag = DEAD_DEAD;
-
-       if(time >= self.ltime)
-       {
-               Monster_Fade();
-               return;
-       }
-       
-       self.SendFlags |= MSF_MOVE; // keep up to date on the monster's location
-}
-
-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;
-       self.monster_spawnfunc();
-}
-
-float Monster_CheckAppearFlags(entity ent)
-{
-       if not(ent.spawnflags & MONSTERFLAG_APPEAR)
-               return FALSE;
-       
-       ent.think = func_null;
-       ent.nextthink = 0;
-       ent.use = Monster_Appear;
-       ent.flags = FL_MONSTER; // set so this monster can get butchered
-       
-       return TRUE;
-}
-
-void monsters_reset()
-{
-       setorigin(self, self.pos1);
-       self.angles = self.pos2;
-       
-       self.health = self.max_health;
-       self.velocity = '0 0 0';
-       self.enemy = world;
-       self.goalentity = world;
-       self.attack_finished_single = 0;
-       self.moveto = self.origin;
-       
-       WaypointSprite_UpdateHealth(self.sprite, self.health);
-}
-
-float monster_send(entity to, float sf)
-{
-       WriteByte(MSG_ENTITY, ENT_CLIENT_MONSTER);    
-       WriteByte(MSG_ENTITY, sf);
-       if(sf & MSF_SETUP)
-       {
-           WriteByte(MSG_ENTITY, self.monsterid);
-           
-           WriteCoord(MSG_ENTITY, self.origin_x);
-           WriteCoord(MSG_ENTITY, self.origin_y);
-           WriteCoord(MSG_ENTITY, self.origin_z);
-           
-           WriteAngle(MSG_ENTITY, self.angles_x);
-           WriteAngle(MSG_ENTITY, self.angles_y);
-               
-               WriteByte(MSG_ENTITY, self.skin);
-               WriteByte(MSG_ENTITY, self.team);
-    }
-    
-    if(sf & MSF_ANG)
-    {
-        WriteShort(MSG_ENTITY, rint(self.angles_x));
-        WriteShort(MSG_ENTITY, rint(self.angles_y));
-    }
-    
-    if(sf & MSF_MOVE)
-    {
-        WriteShort(MSG_ENTITY, rint(self.origin_x));
-        WriteShort(MSG_ENTITY, rint(self.origin_y));
-        WriteShort(MSG_ENTITY, rint(self.origin_z));
-
-        WriteShort(MSG_ENTITY, rint(self.velocity_x));
-        WriteShort(MSG_ENTITY, rint(self.velocity_y));
-        WriteShort(MSG_ENTITY, rint(self.velocity_z));        
-        
-        WriteShort(MSG_ENTITY, rint(self.angles_y));        
-    }
-    
-    if(sf & MSF_ANIM)
-    {
-        WriteCoord(MSG_ENTITY, self.anim_start_time);
-        WriteByte(MSG_ENTITY, self.frame);
-    }
-    
-    if(sf & MSF_STATUS)
-    {
-               WriteByte(MSG_ENTITY, self.skin);
-               
-        WriteByte(MSG_ENTITY, self.team);
-               
-               WriteByte(MSG_ENTITY, self.deadflag);
-        
-        if(self.health <= 0)
-            WriteByte(MSG_ENTITY, 0);
-        else
-            WriteByte(MSG_ENTITY, ceil((self.health / self.max_health) * 255));
-    }
-    
-       return TRUE;
-}
-
-void monster_link(void() spawnproc)
-{
-    Net_LinkEntity(self, TRUE, 0, monster_send);
-    self.think      = spawnproc;
-    self.nextthink  = time;
-}
-
-void monsters_corpse_damage (entity inflictor, entity attacker, float damage, float deathtype, vector hitloc, vector force)
-{
-       self.health -= damage;
-               
-       Violence_GibSplash_At(hitloc, force, 2, bound(0, damage, 200) / 16, self, attacker);
-               
-       if(self.health <= -100) // 100 health until gone?
-       {
-               Violence_GibSplash_At(hitloc, force, 2, bound(0, damage, 200) / 16, self, attacker);
-               
-               self.think = SUB_Remove;
-               self.nextthink = time + 0.1;
-       }
-}
-
-void monsters_damage (entity inflictor, entity attacker, float damage, float deathtype, vector hitloc, vector force)
-{
-       if(self.frozen && deathtype != DEATH_KILL)
-               return;
-               
-       if(time < self.pain_finished && deathtype != DEATH_KILL)
-               return;
-               
-       if(time < self.spawnshieldtime)
-               return;
-               
-       if(deathtype != DEATH_KILL)
-               damage *= self.armorvalue;
-               
-       if(self.weaponentity && self.weaponentity.classname == "shield")
-               self.weaponentity.health -= damage;
-               
-       self.health -= damage;
-       
-       if(self.sprite)
-               WaypointSprite_UpdateHealth(self.sprite, self.health);
-               
-       self.dmg_time = time;
-
-       if(sound_allowed(MSG_BROADCAST, attacker) && deathtype != DEATH_DROWN)
-               spamsound (self, CH_PAIN, "misc/bodyimpact1.wav", VOL_BASE, ATTN_NORM);  // FIXME: PLACEHOLDER
-       
-       self.velocity += force * self.damageforcescale;
-               
-       if(deathtype != DEATH_DROWN)
-       {
-               Violence_GibSplash_At(hitloc, force, 2, bound(0, damage, 200) / 16, self, attacker);
-               if (damage > 50)
-                       Violence_GibSplash_At(hitloc, force * -0.1, 3, 1, self, attacker);
-               if (damage > 100)
-                       Violence_GibSplash_At(hitloc, force * -0.2, 3, 1, self, attacker);
-       }
-               
-       if(self.health <= 0)
-       {        
-               if(self.sprite)
-               {
-                       // Update one more time to avoid waypoint fading without emptying healthbar
-                       WaypointSprite_UpdateHealth(self.sprite, 0);
-               }
-               
-               if(deathtype == DEATH_KILL)
-                       self.candrop = FALSE; // killed by mobkill command
-                       
-               // TODO: fix this?
-               activator = attacker;
-               other = self.enemy;
-               SUB_UseTargets();
-               self.target2 = self.oldtarget2; // reset to original target on death, incase we respawn
-       
-               self.monster_die();
-               
-               frag_attacker = attacker;
-               frag_target = self;
-               MUTATOR_CALLHOOK(MonsterDies);
-               
-               if(self.health <= -100) // check if we're already gibbed
-               {
-                       Violence_GibSplash(self, 1, 0.5, attacker);
-               
-                       self.think = SUB_Remove;
-                       self.nextthink = time + 0.1;
-               }
-       }
-       
-       self.SendFlags |= MSF_STATUS;
-}
-
-// post-death functions
-void monster_hook_death()
-{
-       WaypointSprite_Kill(self.sprite);
-               
-       if(self.weaponentity)
-       {
-               remove(self.weaponentity);
-               self.weaponentity = world;
-       }
-               
-       monster_sound(self.msound_death, 0, FALSE);
-               
-       if(!(self.spawnflags & MONSTERFLAG_SPAWNED) && !self.monster_respawned)
-               monsters_killed += 1;
-               
-       if(self.candrop && self.weapon)
-               W_ThrowNewWeapon(self, self.weapon, 0, self.origin, randomvec() * 150 + '0 0 325');     
-               
-       if(IS_CLIENT(self.realowner))
-               self.realowner.monstercount -= 1;
-               
-       self.event_damage       = monsters_corpse_damage;
-       self.solid                      = SOLID_CORPSE;
-       self.takedamage         = DAMAGE_AIM;
-       self.enemy                      = world;
-       self.movetype           = MOVETYPE_TOSS;
-       self.moveto                     = self.origin;
-       self.touch                      = MonsterTouch; // reset incase monster was pouncing
-       
-       if not(self.flags & FL_FLY)
-               self.velocity = '0 0 0';
-       
-       self.SendFlags |= MSF_MOVE;
-               
-       totalspawned -= 1;
-}
-
-// post-spawn functions
-void monster_hook_spawn()
-{
-       if not(self.monster_respawned)
-               Monster_CheckMinibossFlag();
-       
-       self.max_health = self.health;
-       self.pain_finished = self.nextthink;
-       self.anim_start_time = time;
-       
-       if not(self.noalign)
-       {
-               setorigin(self, self.origin + '0 0 20');
-               tracebox(self.origin + '0 0 100', self.mins, self.maxs, self.origin - '0 0 10000', MOVE_WORLDONLY, self);
-               setorigin(self, trace_endpos);
-       }
-       
-       if not(self.monster_respawned)
-       if not(self.skin)
-               self.skin = rint(random() * 4);
-       
-       self.pos1 = self.origin;
-
-       monster_precachesounds(self);
-       
-       if(teamplay)
-               self.monster_attack = TRUE; // we can have monster enemies in team games
-               
-       if(autocvar_g_monsters_healthbars)
-       {
-               WaypointSprite_Spawn(strzone(strdecolorize(self.netname)), 0, 600, self, '0 0 1' * (self.maxs_z + 15), world, 0, self, sprite, FALSE, RADARICON_DANGER, ((self.team) ? Team_ColorRGB(self.team) : '1 0 0'));    
-               WaypointSprite_UpdateMaxHealth(self.sprite, self.max_health);
-               WaypointSprite_UpdateHealth(self.sprite, self.health);
-       }
-       
-       monster_sound(self.msound_spawn, 0, FALSE);
-
-       MUTATOR_CALLHOOK(MonsterSpawn);
-       
-       self.SendFlags = MSF_SETUP;
-}
-
-float monster_initialize(string  net_name, float mon_id,
-                                                vector  min_s,
-                                                vector  max_s,
-                                                float   nodrop,
-                                                void() dieproc,
-                                                void() spawnproc)
-{
-       if not(autocvar_g_monsters)
-               return FALSE;
-               
-       // support for quake style removing monsters based on skill
-       if(monster_skill <= autocvar_g_monsters_skill_easy && (self.spawnflags & MONSTERSKILL_NOTEASY)) { return FALSE; }
-       if(monster_skill == autocvar_g_monsters_skill_normal && (self.spawnflags & MONSTERSKILL_NOTMEDIUM)) { return FALSE; }
-       if(monster_skill == autocvar_g_monsters_skill_hard && (self.spawnflags & MONSTERSKILL_NOTHARD)) { return FALSE; }
-       if(monster_skill == autocvar_g_monsters_skill_insane && (self.spawnflags & MONSTERSKILL_NOTINSANE)) { return FALSE; }
-       if(monster_skill >= autocvar_g_monsters_skill_nightmare && (self.spawnflags & MONSTERSKILL_NOTNIGHTMARE)) { return FALSE; }
-
-       if(self.netname == "")
-               self.netname = ((net_name == "") ? self.classname : net_name);
-       
-       if(self.team && !teamplay)
-               self.team = 0;
-
-       self.flags = FL_MONSTER;
-               
-       if not(self.spawnflags & MONSTERFLAG_SPAWNED) // naturally spawned monster
-       if not(self.monster_respawned)
-               monsters_total += 1;
-
-       setsize(self, min_s, max_s);
-       self.takedamage                 = DAMAGE_AIM;
-       self.bot_attack                 = TRUE;
-       self.iscreature                 = TRUE;
-       self.teleportable               = TRUE;
-       self.damagedbycontents  = TRUE;
-       self.monsterid                  = mon_id;
-       self.damageforcescale   = 0.003;
-       self.monster_die                = dieproc;
-       self.event_damage               = monsters_damage;
-       self.touch                              = MonsterTouch;
-       self.use                                = monster_use;
-       self.solid                              = SOLID_BBOX;
-       self.scale                              = 1;
-       self.movetype                   = MOVETYPE_WALK;
-       self.spawnshieldtime    = time + autocvar_g_monsters_spawnshieldtime;
-       monsters_spawned           += 1;
-       self.enemy                              = world;
-       self.velocity                   = '0 0 0';
-       self.moveto                             = self.origin;
-       self.pos2                               = self.angles;
-       self.reset                              = monsters_reset;
-       self.candrop                    = TRUE;
-       self.view_ofs                   = '0 0 1' * (self.maxs_z * 0.5);
-       self.oldtarget2                 = self.target2;
-       self.deadflag                   = DEAD_NO; // UNDEAD
-       self.noalign                    = nodrop;
-       self.spawn_time                 = time;
-       self.gravity                    = 1;
-       self.dphitcontentsmask  = DPCONTENTS_SOLID | DPCONTENTS_BODY | DPCONTENTS_BOTCLIP | DPCONTENTS_MONSTERCLIP;
-       
-       if not(self.attack_range)
-               self.attack_range = 120;
-       
-       if not(self.ticrate)
-               self.ticrate = autocvar_g_monsters_think_delay;
-               
-       self.ticrate = bound(sys_frametime, self.ticrate, 60);
-       
-       if not(self.armorvalue)
-               self.armorvalue = 1; // multiplier
-       
-       if not(self.target_range)
-               self.target_range = autocvar_g_monsters_target_range;
-       
-       if not(self.respawntime)
-               self.respawntime = autocvar_g_monsters_respawn_delay;
-       
-       if not(self.monster_moveflags)
-               self.monster_moveflags = MONSTER_MOVE_WANDER;
-       
-       monster_link(spawnproc);
-
-       return TRUE;
-}
diff --git a/qcsrc/server/monsters/lib/monsters_early.qh b/qcsrc/server/monsters/lib/monsters_early.qh
deleted file mode 100644 (file)
index 9c7fc1e..0000000
+++ /dev/null
@@ -1,90 +0,0 @@
-// for definitions used outside the monsters folder
-
-#ifdef SVQC
-.string spawnmob;
-.float monster_attack;
-
-float monster_skill;
-float spawncode_first_load; // used to tell the player the monster database is loading (TODO: fix this?)
-
-.entity monster_owner; // new monster owner entity, fixes non-solid monsters
-.float monstercount; // per player monster count
-
-.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
-#endif // SVQC
-
-#ifndef MENUQC
-.float monsterid;
-// Monster IDs
-float MONSTER_FIRST            = 1;
-float MONSTER_ZOMBIE           = 2;
-float MONSTER_BRUTE            = 3;
-float MONSTER_ANIMUS           = 4;
-float MONSTER_SHAMBLER                 = 5;
-float MONSTER_BRUISER          = 6;
-float MONSTER_WYVERN           = 7;
-float MONSTER_CERBERUS         = 8;
-float MONSTER_SLIME            = 9;
-float MONSTER_KNIGHT           = 10;
-float MONSTER_STINGRAY                 = 11;
-float MONSTER_MAGE             = 12;
-float MONSTER_SPIDER           = 13;
-float MONSTER_LAST             = 14;
-
-// id-string converters (TODO: remove these!)
-string monster_id2string(float mnster)
-{
-       switch(mnster)
-       {
-               case MONSTER_ZOMBIE:    return "zombie";
-               case MONSTER_BRUTE:     return "brute";
-               case MONSTER_ANIMUS:    return "animus";
-               case MONSTER_SHAMBLER:  return "shambler";
-               case MONSTER_BRUISER:   return "bruiser";
-               case MONSTER_WYVERN:    return "wyvern";
-               case MONSTER_CERBERUS:  return "cerberus";
-               case MONSTER_SLIME:     return "slime";
-               case MONSTER_KNIGHT:    return "knight";
-               case MONSTER_STINGRAY:  return "stingray";
-               case MONSTER_MAGE:              return "mage";
-               case MONSTER_SPIDER:    return "spider";
-               default: return "";
-       }
-}
-float monster_string2id(string monster)
-{
-       switch(monster)
-       {
-               case "zombie":          return MONSTER_ZOMBIE;
-               case "brute":           return MONSTER_BRUTE;
-               case "animus":          return MONSTER_ANIMUS;
-               case "shambler":        return MONSTER_SHAMBLER;
-               case "bruiser":         return MONSTER_BRUISER;
-               case "wyvern":          return MONSTER_WYVERN;
-               case "cerberus":        return MONSTER_CERBERUS;
-               case "slime":           return MONSTER_SLIME;
-               case "knight":          return MONSTER_KNIGHT;
-               case "stingray":        return MONSTER_STINGRAY;
-               case "mage":            return MONSTER_MAGE;
-               case "spider":          return MONSTER_SPIDER;
-               default:                        return 0;
-       }
-}
-
-.float anim_start_time;
-
-float MSF_UPDATE       = 2;
-float MSF_STATUS       = 4;
-float MSF_SETUP        = 8;
-float MSF_ANG          = 16;
-float MSF_MOVE         = 32;
-float MSF_ANIM         = 64;
-
-float MSF_FULL_UPDATE  = 16777215;
-
-#endif // CSQC/SVQC
diff --git a/qcsrc/server/monsters/lib/spawn.qc b/qcsrc/server/monsters/lib/spawn.qc
deleted file mode 100644 (file)
index b985c7f..0000000
+++ /dev/null
@@ -1,57 +0,0 @@
-entity spawnmonster (string monster, float mnster, entity spawnedby, entity own, vector orig, float respwn, float moveflag)
-{
-       if(!spawncode_first_load)
-       {
-               initialize_field_db();
-               spawncode_first_load = TRUE;
-       }
-       
-       entity e = spawn();
-       
-       e.spawnflags = MONSTERFLAG_SPAWNED;
-       
-       if not(respwn)
-               e.spawnflags |= MONSTERFLAG_NORESPAWN;
-       
-       setorigin(e, orig);
-       
-       if(monster != "")
-       if not(monster_string2id(monster))
-               monster = "bruiser";
-               
-       if(monster == "")
-       if(mnster)
-               monster = monster_id2string(mnster);
-       
-       e.realowner = spawnedby;
-       
-       if(moveflag)
-               e.monster_moveflags = moveflag;
-       
-       if (spawnedby.classname == "td_spawnpoint")
-       {
-               e.monster_owner = own;
-               e.team = spawnedby.team;
-       }
-       else if(IS_PLAYER(spawnedby))
-       {
-               if(teamplay && autocvar_g_monsters_teams)
-                       e.team = spawnedby.team; // colors handled in spawn code
-                       
-               if(e.team)
-                       e.colormap = 1024;
-               else
-                       e.colormap = spawnedby.colormap;
-                       
-               if(autocvar_g_monsters_owners)
-                       e.monster_owner = own; // using .owner makes the monster non-solid for its master
-                       
-               e.angles = spawnedby.angles;
-       }
-               
-       monster = strcat("$ spawnfunc_monster_", monster);
-               
-       target_spawn_edit_entity(e, monster, world, world, world, world, world);
-               
-       return e;
-}
diff --git a/qcsrc/server/monsters/monster/animus.qc b/qcsrc/server/monsters/monster/animus.qc
deleted file mode 100644 (file)
index cfb2292..0000000
+++ /dev/null
@@ -1,127 +0,0 @@
-const vector ANIMUS_MIN = '-41 -41 -31';
-const vector ANIMUS_MAX = '41 41 31';
-
-string ANIMUS_MODEL = "models/monsters/demon.mdl";
-
-#ifdef SVQC
-float autocvar_g_monster_animus;
-float autocvar_g_monster_animus_health;
-float autocvar_g_monster_animus_attack_jump_damage;
-float autocvar_g_monster_animus_damage;
-float autocvar_g_monster_animus_speed_walk;
-float autocvar_g_monster_animus_speed_run;
-
-const float animus_anim_stand  = 0;
-const float animus_anim_walk   = 1;
-const float animus_anim_run            = 2;
-const float animus_anim_leap   = 3;
-const float animus_anim_pain   = 4;
-const float animus_anim_death  = 5;
-const float animus_anim_attack = 6;
-
-void animus_think()
-{
-       self.think = animus_think;
-       self.nextthink = time + self.ticrate;
-       
-       monster_move(autocvar_g_monster_animus_speed_run, autocvar_g_monster_animus_speed_walk, 100, animus_anim_run, animus_anim_walk, animus_anim_stand);
-}
-
-void animus_touch_jump()
-{
-       if (self.health <= 0)
-               return;
-
-       if (monster_isvalidtarget(other, self))
-       {
-               if (vlen(self.velocity) > 300)
-               {
-                       Damage(other, self, self, autocvar_g_monster_animus_attack_jump_damage * monster_skill, DEATH_MONSTER_ANIMUS, other.origin, normalize(other.origin - self.origin));
-                       self.touch = MonsterTouch; // instantly turn it off to stop damage spam
-               }
-       }
-
-       if(trace_dphitcontents)
-               self.touch = MonsterTouch;
-}
-
-float animus_attack(float attack_type)
-{
-       switch(attack_type)
-       {
-               case MONSTER_ATTACK_MELEE:
-               {
-                       monsters_setframe(animus_anim_attack);
-                       self.attack_finished_single = time + 1;
-                       monster_melee(self.enemy, autocvar_g_monster_animus_damage, 0.3, DEATH_MONSTER_ANIMUS, TRUE);
-                       
-                       return TRUE;
-               }
-               case MONSTER_ATTACK_RANGED:
-               {
-                       makevectors(self.angles);
-                       if(monster_leap(animus_anim_leap, animus_touch_jump, v_forward * 700 + '0 0 300', 0.8))
-                               return TRUE;
-               }
-       }
-       
-       return FALSE;
-}
-
-void animus_die()
-{
-       Monster_CheckDropCvars ("animus");
-       
-       self.think = monster_dead_think;
-       self.nextthink = time + self.ticrate;
-       self.ltime = time + 5;
-       monsters_setframe(animus_anim_death);
-       
-       monster_hook_death(); // for post-death mods
-}
-
-void animus_spawn()
-{
-       if not(self.health)
-               self.health = autocvar_g_monster_animus_health;
-
-       self.damageforcescale   = 0;
-       self.classname                  = "monster_animus";
-       self.monster_attackfunc = animus_attack;
-       self.nextthink                  = time + random() * 0.5 + 0.1;
-       self.think                              = animus_think;
-       
-       monsters_setframe(animus_anim_stand);
-       
-       monster_setupsounds("animus");
-       
-       monster_hook_spawn(); // for post-spawn mods
-}
-
-void spawnfunc_monster_animus()
-{
-       if not(autocvar_g_monster_animus) { remove(self); return; }
-       
-       self.monster_spawnfunc = spawnfunc_monster_animus;
-       
-       if(Monster_CheckAppearFlags(self))
-               return;
-       
-       self.scale = 1.3;
-       
-       if not (monster_initialize(
-                        "Animus", MONSTER_ANIMUS,
-                        ANIMUS_MIN, ANIMUS_MAX,
-                        FALSE,
-                        animus_die, animus_spawn))
-       {
-               remove(self);
-               return;
-       }
-}
-
-// compatibility with old spawns
-void spawnfunc_monster_demon1() { spawnfunc_monster_animus(); }
-void spawnfunc_monster_demon() { spawnfunc_monster_animus(); }
-
-#endif // SVQC
diff --git a/qcsrc/server/monsters/monster/bruiser.qc b/qcsrc/server/monsters/monster/bruiser.qc
deleted file mode 100644 (file)
index e19066b..0000000
+++ /dev/null
@@ -1,105 +0,0 @@
-const vector BRUISER_MIN = '-20 -20 -31';
-const vector BRUISER_MAX = '20 20 53';
-
-string BRUISER_MODEL = "models/monsters/knight.mdl";
-
-#ifdef SVQC
-float autocvar_g_monster_bruiser;
-float autocvar_g_monster_bruiser_health;
-float autocvar_g_monster_bruiser_melee_damage;
-float autocvar_g_monster_bruiser_speed_walk;
-float autocvar_g_monster_bruiser_speed_run;
-
-const float bruiser_anim_stand                 = 0;
-const float bruiser_anim_run           = 1;
-const float bruiser_anim_runattack     = 2;
-const float bruiser_anim_pain1                 = 3;
-const float bruiser_anim_pain2                 = 4;
-const float bruiser_anim_attack        = 5;
-const float bruiser_anim_walk          = 6;
-const float bruiser_anim_kneel                 = 7;
-const float bruiser_anim_standing      = 8;
-const float bruiser_anim_death1        = 9;
-const float bruiser_anim_death2        = 10;
-
-void bruiser_think()
-{
-       self.think = bruiser_think;
-       self.nextthink = time + self.ticrate;
-       
-       monster_move(autocvar_g_monster_bruiser_speed_run, autocvar_g_monster_bruiser_speed_walk, 50, bruiser_anim_run, bruiser_anim_walk, bruiser_anim_stand);
-}
-
-float bruiser_attack(float attack_type)
-{
-       switch(attack_type)
-       {
-               case MONSTER_ATTACK_MELEE:
-               {
-                       float len = vlen(self.velocity);
-                       monsters_setframe((len < 50) ? bruiser_anim_attack : bruiser_anim_runattack);
-                       self.attack_finished_single = time + 1.25;
-                       
-                       monster_melee(self.enemy, autocvar_g_monster_bruiser_melee_damage, 0.3, DEATH_MONSTER_BRUISER, FALSE);
-                       
-                       return TRUE;
-               }
-               case MONSTER_ATTACK_RANGED:
-       }
-       
-       return FALSE;
-}
-
-void bruiser_die()
-{
-       Monster_CheckDropCvars ("bruiser");
-       
-       self.think = monster_dead_think;
-       self.nextthink = time + self.ticrate;
-       self.ltime = time + 5;
-       monsters_setframe((random() > 0.5) ? bruiser_anim_death1 : bruiser_anim_death2);
-       
-       monster_hook_death(); // for post-death mods
-}
-
-void bruiser_spawn()
-{
-       if not(self.health)
-               self.health = autocvar_g_monster_bruiser_health;
-
-       self.damageforcescale   = 0.003;
-       self.classname                  = "monster_bruiser";
-       self.monster_attackfunc = bruiser_attack;
-       self.nextthink                  = time + random() * 0.5 + 0.1;
-       self.think                              = bruiser_think;
-       
-       monsters_setframe(bruiser_anim_stand);
-       
-       monster_setupsounds("bruiser");
-       
-       monster_hook_spawn(); // for post-spawn mods
-}
-
-void spawnfunc_monster_bruiser()
-{
-       if not(autocvar_g_monster_bruiser) { remove(self); return; }
-       
-       self.monster_spawnfunc = spawnfunc_monster_bruiser;
-       
-       if(Monster_CheckAppearFlags(self))
-               return;
-       
-       self.scale = 1.3;
-       
-       if not (monster_initialize(
-                        "Bruiser", MONSTER_BRUISER,
-                        BRUISER_MIN, BRUISER_MAX,
-                        FALSE,
-                        bruiser_die, bruiser_spawn))
-       {
-               remove(self);
-               return;
-       }
-}
-
-#endif // SVQC
diff --git a/qcsrc/server/monsters/monster/brute.qc b/qcsrc/server/monsters/monster/brute.qc
deleted file mode 100644 (file)
index 8952d54..0000000
+++ /dev/null
@@ -1,237 +0,0 @@
-const vector BRUTE_MIN = '-36 -36 -20';
-const vector BRUTE_MAX = '36 36 50';
-
-string BRUTE_MODEL = "models/monsters/ogre.dpm";
-
-#ifdef SVQC
-float autocvar_g_monster_brute;
-float autocvar_g_monster_brute_health;
-float autocvar_g_monster_brute_chainsaw_damage;
-float autocvar_g_monster_brute_speed_walk;
-float autocvar_g_monster_brute_speed_run;
-float autocvar_g_monster_brute_attack_uzi_bullets;
-float autocvar_g_monster_brute_attack_uzi_damage;
-float autocvar_g_monster_brute_attack_uzi_force;
-float autocvar_g_monster_brute_attack_uzi_chance;
-float autocvar_g_monster_brute_attack_grenade_damage;
-float autocvar_g_monster_brute_attack_grenade_edgedamage;
-float autocvar_g_monster_brute_attack_grenade_force;
-float autocvar_g_monster_brute_attack_grenade_radius;
-
-const float brute_anim_idle            = 0;
-const float brute_anim_walk            = 1;
-const float brute_anim_run                     = 2;
-const float brute_anim_pain            = 3;
-const float brute_anim_swing           = 4;
-const float brute_anim_die                     = 5;
-
-.float brute_cycles;
-
-void brute_think()
-{
-       self.think = brute_think;
-       self.nextthink = time + self.ticrate;
-       
-       monster_move(autocvar_g_monster_brute_speed_run, autocvar_g_monster_brute_speed_walk, 300, brute_anim_run, brute_anim_walk, brute_anim_idle);
-}
-
-void brute_blade()
-{
-       self.brute_cycles += 1;
-       self.angles_y = self.angles_y + random()* 25;
-       
-       monster_melee(self.enemy, autocvar_g_monster_brute_chainsaw_damage, 0.3, DEATH_MONSTER_BRUTE_BLADE, TRUE);
-       
-       if(self.brute_cycles <= 4)
-               defer(0.2, brute_blade);
-}
-
-void brute_uzi()
-{
-       self.brute_cycles += 1;
-       
-       monster_makevectors(self.enemy);
-       
-       W_SetupShot (self, autocvar_g_antilag_bullets && 18000 >= autocvar_g_antilag_bullets, 0, "weapons/uzi_fire.wav", CH_WEAPON_A, autocvar_g_monster_brute_attack_uzi_damage);
-       fireBallisticBullet(w_shotorg, w_shotdir, 0.02, 18000, 5, autocvar_g_monster_brute_attack_uzi_damage, autocvar_g_monster_brute_attack_uzi_force, DEATH_MONSTER_BRUTE_UZI, 0, 1, 115);
-       endFireBallisticBullet();
-       
-       if(self.brute_cycles <= autocvar_g_monster_brute_attack_uzi_bullets)
-               defer(0.1, brute_uzi);
-}
-
-void brute_grenade_explode()
-{
-       pointparticles(particleeffectnum("grenade_explode"), self.origin, '0 0 0', 1);
-       sound(self, CH_SHOTS, "weapons/grenade_impact.wav", VOL_BASE, ATTN_NORM);
-
-       self.event_damage = func_null;
-       self.takedamage = DAMAGE_NO;
-
-       if(self.movetype == MOVETYPE_NONE)
-               self.velocity = self.oldvelocity;
-
-       RadiusDamage (self, self.realowner, autocvar_g_monster_brute_attack_grenade_damage, autocvar_g_monster_brute_attack_grenade_edgedamage, autocvar_g_monster_brute_attack_grenade_radius, world, autocvar_g_monster_brute_attack_grenade_force, self.projectiledeathtype, other);
-
-       remove (self);
-}
-
-void brute_grenade_damage(entity inflictor, entity attacker, float damage, float deathtype, vector hitloc, vector force)
-{
-       if (self.health <= 0)
-               return;
-               
-       if (!W_CheckProjectileDamage(inflictor.realowner, self.realowner, deathtype, -1)) // no exceptions
-               return; // g_projectiles_damage says to halt
-               
-       self.health = self.health - damage;
-       
-       if (self.health <= 0)
-               W_PrepareExplosionByDamage(attacker, self.use);
-}
-
-void brute_grenade_touch()
-{
-       PROJECTILE_TOUCH;
-       
-       self.use ();
-}
-
-void brute_grenade_think()
-{
-       self.nextthink = time;
-       if (time > self.cnt)
-       {
-               other = world;
-               brute_grenade_explode();
-               return;
-       }
-}
-
-void brute_grenade()
-{
-       entity gren;
-
-       W_SetupShot_ProjectileSize (self, '-3 -3 -3', '3 3 3', FALSE, 4, "weapons/grenade_fire.wav", CH_WEAPON_A, autocvar_g_monster_brute_attack_grenade_damage);
-       w_shotdir = v_forward; // no TrueAim for grenades please
-
-       gren = spawn ();
-       gren.owner = gren.realowner = self;
-       gren.classname = "grenade";
-       gren.bot_dodge = TRUE;
-       gren.bot_dodgerating = autocvar_g_monster_brute_attack_grenade_damage;
-       gren.movetype = MOVETYPE_BOUNCE;
-       PROJECTILE_MAKETRIGGER(gren);
-       gren.projectiledeathtype = DEATH_MONSTER_BRUTE_GRENADE;
-       setorigin(gren, w_shotorg);
-       setsize(gren, '-3 -3 -3', '3 3 3');
-
-       gren.cnt = time + 5;
-       gren.nextthink = time;
-       gren.think = brute_grenade_think;
-       gren.use = brute_grenade_explode;
-       gren.touch = brute_grenade_touch;
-
-       gren.takedamage = DAMAGE_YES;
-       gren.health = autocvar_g_balance_grenadelauncher_primary_health;
-       gren.damageforcescale = autocvar_g_balance_grenadelauncher_primary_damageforcescale;
-       gren.event_damage = brute_grenade_damage;
-       gren.damagedbycontents = TRUE;
-       gren.missile_flags = MIF_SPLASH | MIF_ARC;
-       W_SETUPPROJECTILEVELOCITY_UP(gren, g_balance_grenadelauncher_primary);
-
-       gren.angles = vectoangles (gren.velocity);
-       gren.flags = FL_PROJECTILE;
-
-       CSQCProjectile(gren, TRUE, PROJECTILE_GRENADE, TRUE);
-}
-
-float brute_attack(float attack_type)
-{
-       switch(attack_type)
-       {
-               case MONSTER_ATTACK_MELEE:
-               {
-                       self.brute_cycles = 0;
-                       monsters_setframe(brute_anim_swing);
-                       self.attack_finished_single = time + 1.3;
-                       brute_blade();
-                       
-                       return TRUE;
-               }
-               case MONSTER_ATTACK_RANGED:
-               {
-                       self.brute_cycles = 0;
-                       if(random() <= autocvar_g_monster_brute_attack_uzi_chance)
-                       {
-                               monsters_setframe(brute_anim_pain);
-                               self.attack_finished_single = time + 0.8;
-                               defer(0.1, brute_uzi);
-                       }
-                       else
-                       {
-                               monster_makevectors(self.enemy);
-                               brute_grenade();
-                               monsters_setframe(brute_anim_pain);
-                               self.attack_finished_single = time + 1.2;
-                       }
-                       
-                       return TRUE;
-               }
-       }
-       
-       return FALSE;
-}
-
-void brute_die()
-{
-       Monster_CheckDropCvars ("brute");
-       
-       self.think = monster_dead_think;
-       self.nextthink = time + self.ticrate;
-       self.ltime = time + 5;
-       monsters_setframe(brute_anim_die);
-               
-       monster_hook_death(); // for post-death mods
-}
-
-void brute_spawn()
-{
-       if not(self.health)
-               self.health = autocvar_g_monster_brute_health;
-
-       self.damageforcescale   = 0.003;
-       self.classname                  = "monster_brute";
-       self.monster_attackfunc = brute_attack;
-       self.nextthink                  = time + random() * 0.5 + 0.1;
-       self.think                              = brute_think;
-       self.weapon                             = WEP_GRENADE_LAUNCHER;
-       
-       monsters_setframe(brute_anim_idle);
-       
-       monster_setupsounds("brute");
-       
-       monster_hook_spawn(); // for post-spawn mods
-}
-
-void spawnfunc_monster_brute()
-{
-       if not(autocvar_g_monster_brute) { remove(self); return; }
-       
-       self.monster_spawnfunc = spawnfunc_monster_brute;
-       
-       if(Monster_CheckAppearFlags(self))
-               return;
-       
-       if not (monster_initialize(
-                        "Brute", MONSTER_BRUTE,
-                        BRUTE_MIN, BRUTE_MAX,
-                        FALSE,
-                        brute_die, brute_spawn))
-       {
-               remove(self);
-               return;
-       }
-}
-
-#endif // SVQC
diff --git a/qcsrc/server/monsters/monster/cerberus.qc b/qcsrc/server/monsters/monster/cerberus.qc
deleted file mode 100644 (file)
index 38b57e7..0000000
+++ /dev/null
@@ -1,118 +0,0 @@
-const vector CERBERUS_MIN = '-16 -16 -24';
-const vector CERBERUS_MAX = '16 16 12';
-
-string CERBERUS_MODEL = "models/monsters/dog.dpm";
-
-#ifdef SVQC
-float autocvar_g_monster_cerberus;
-float autocvar_g_monster_cerberus_health;
-float autocvar_g_monster_cerberus_bite_damage;
-float autocvar_g_monster_cerberus_attack_jump_damage;
-float autocvar_g_monster_cerberus_speed_walk;
-float autocvar_g_monster_cerberus_speed_run;
-
-const float cerberus_anim_idle = 0;
-const float cerberus_anim_walk = 1;
-const float cerberus_anim_run  = 2;
-const float cerberus_anim_attack       = 3;
-const float cerberus_anim_die  = 4;
-const float cerberus_anim_pain = 5;
-
-void cerberus_think()
-{
-       self.think = cerberus_think;
-       self.nextthink = time + self.ticrate;
-       
-       monster_move(autocvar_g_monster_cerberus_speed_run, autocvar_g_monster_cerberus_speed_walk, 50, cerberus_anim_run, cerberus_anim_walk, cerberus_anim_idle);
-}
-
-void cerberus_touch_jump()
-{
-       if (other.takedamage)
-       if (vlen(self.velocity) > 300)
-       {
-               Damage(self.enemy, self, self, autocvar_g_monster_cerberus_attack_jump_damage * monster_skill, DEATH_MONSTER_CERBERUS_JUMP, self.enemy.origin, normalize(self.enemy.origin - self.origin));
-               self.touch = MonsterTouch;
-       }
-
-       if(trace_dphitcontents)
-               self.touch = MonsterTouch;
-}
-
-float cerberus_attack(float attack_type)
-{
-       switch(attack_type)
-       {
-               case MONSTER_ATTACK_MELEE:
-               {
-                       monsters_setframe(cerberus_anim_attack);
-                       self.attack_finished_single = time + 0.7;
-                       monster_melee(self.enemy, autocvar_g_monster_cerberus_bite_damage, 0.2, DEATH_MONSTER_CERBERUS_BITE, TRUE);
-                       
-                       return TRUE;
-               }
-               case MONSTER_ATTACK_RANGED:
-               {
-                       makevectors(self.angles);
-                       if(monster_leap(cerberus_anim_attack, cerberus_touch_jump, v_forward * 300 + '0 0 200', 0.8))
-                               return TRUE;
-               }
-       }
-       
-       return FALSE;
-}
-
-void cerberus_die()
-{
-       Monster_CheckDropCvars ("cerberus");
-       
-       self.think = monster_dead_think;
-       self.nextthink = time + self.ticrate;
-       self.ltime = time + 5;
-       monsters_setframe(cerberus_anim_die);
-       
-       monster_hook_death(); // for post-death mods
-}
-
-void cerberus_spawn()
-{
-       if not(self.health)
-               self.health = autocvar_g_monster_cerberus_health;
-
-       self.damageforcescale   = 0;
-       self.classname                  = "monster_cerberus";
-       self.monster_attackfunc = cerberus_attack;
-       self.nextthink                  = time + random() * 0.5 + 0.1;
-       self.think                              = cerberus_think;
-       
-       monsters_setframe(cerberus_anim_idle);
-       
-       monster_setupsounds("cerberus");
-       
-       monster_hook_spawn(); // for post-spawn mods
-}
-
-void spawnfunc_monster_cerberus()
-{
-       if not(autocvar_g_monster_cerberus) { remove(self); return; }
-       
-       self.monster_spawnfunc = spawnfunc_monster_cerberus;
-       
-       if(Monster_CheckAppearFlags(self))
-               return;
-       
-       if not (monster_initialize(
-                        "Cerberus", MONSTER_CERBERUS,
-                        CERBERUS_MIN, CERBERUS_MAX,
-                        FALSE,
-                        cerberus_die, cerberus_spawn))
-       {
-               remove(self);
-               return;
-       }
-}
-
-// compatibility with old spawns
-void spawnfunc_monster_dog() { spawnfunc_monster_cerberus(); }
-
-#endif // SVQC
diff --git a/qcsrc/server/monsters/monster/knight.qc b/qcsrc/server/monsters/monster/knight.qc
deleted file mode 100644 (file)
index 816ac60..0000000
+++ /dev/null
@@ -1,319 +0,0 @@
-const vector KNIGHT_MIN = '-20 -20 -32';
-const vector KNIGHT_MAX = '20 20 41';
-
-string KNIGHT_MODEL = "models/monsters/hknight.mdl";
-
-#ifdef SVQC
-float autocvar_g_monster_knight;
-float autocvar_g_monster_knight_health;
-float autocvar_g_monster_knight_melee_damage;
-float autocvar_g_monster_knight_inferno_damage;
-float autocvar_g_monster_knight_inferno_damagetime;
-float autocvar_g_monster_knight_inferno_chance;
-float autocvar_g_monster_knight_speed_walk;
-float autocvar_g_monster_knight_speed_run;
-float autocvar_g_monster_knight_fireball_damage;
-float autocvar_g_monster_knight_fireball_force;
-float autocvar_g_monster_knight_fireball_radius;
-float autocvar_g_monster_knight_fireball_chance;
-float autocvar_g_monster_knight_fireball_edgedamage;
-float autocvar_g_monster_knight_spike_chance;
-float autocvar_g_monster_knight_spike_force;
-float autocvar_g_monster_knight_spike_radius;
-float autocvar_g_monster_knight_spike_edgedamage;
-float autocvar_g_monster_knight_spike_damage;
-float autocvar_g_monster_knight_jump_chance;
-float autocvar_g_monster_knight_jump_damage;
-float autocvar_g_monster_knight_jump_dist;
-
-const float knight_anim_stand  = 0;
-const float knight_anim_walk   = 1;
-const float knight_anim_run    = 2;
-const float knight_anim_pain   = 3;
-const float knight_anim_death1         = 4;
-const float knight_anim_death2         = 5;
-const float knight_anim_charge1 = 6;
-const float knight_anim_magic1         = 7;
-const float knight_anim_magic2         = 8;
-const float knight_anim_charge2 = 9;
-const float knight_anim_slice  = 10;
-const float knight_anim_smash  = 11;
-const float knight_anim_wattack = 12;
-const float knight_anim_magic3         = 13;
-
-.float knight_cycles;
-
-void knight_think()
-{
-       self.think = knight_think;
-       self.nextthink = time + self.ticrate;
-       
-       monster_move(autocvar_g_monster_knight_speed_run, autocvar_g_monster_knight_speed_walk, 100, knight_anim_run, knight_anim_walk, knight_anim_stand);
-}
-
-void knight_inferno()
-{
-       if not(self.enemy)
-               return;
-               
-       traceline((self.absmin + self.absmax) * 0.5, (self.enemy.absmin + self.enemy.absmax) * 0.5, TRUE, world);
-       if (trace_fraction != 1)
-               return; // not visible
-       
-       self.enemy.effects |= EF_MUZZLEFLASH;
-       sound(self.enemy, CHAN_AUTO, "player/lava.wav", 1, ATTN_NORM);
-       
-       if(vlen(self.enemy.origin - self.origin) <= 2000)
-               Fire_AddDamage(self.enemy, self, autocvar_g_monster_knight_inferno_damage * monster_skill, autocvar_g_monster_knight_inferno_damagetime, DEATH_MONSTER_KNIGHT_INFERNO);
-}
-
-void knight_fireball_explode()
-{
-       entity e;
-       if(self)
-       {
-               pointparticles(particleeffectnum("fireball_explode"), self.origin, '0 0 0', 1);
-               
-               RadiusDamage(self, self.realowner, autocvar_g_monster_knight_fireball_damage, autocvar_g_monster_knight_fireball_edgedamage, autocvar_g_monster_knight_fireball_force, world, autocvar_g_monster_knight_fireball_radius, self.projectiledeathtype, world);
-               
-               for(e = world; (e = findfloat(e, takedamage, DAMAGE_AIM)); ) if(vlen(e.origin - self.origin) <= autocvar_g_monster_knight_fireball_radius)
-                       Fire_AddDamage(e, self, 5 * monster_skill, autocvar_g_monster_knight_inferno_damagetime, self.projectiledeathtype);
-               
-               remove(self);
-       }
-}
-
-void knight_fireball_touch()
-{
-       PROJECTILE_TOUCH;
-       
-       knight_fireball_explode();
-}
-
-void knight_fireball()
-{
-       entity missile = spawn();
-       vector dir = normalize((self.enemy.origin + '0 0 10') - self.origin);
-       
-       monster_makevectors(self.enemy);
-       
-       self.effects |= EF_MUZZLEFLASH;
-       sound(self, CHAN_WEAPON, "weapons/fireball2.wav", 1, ATTN_NORM);
-
-       missile.owner = missile.realowner = self;
-       missile.solid = SOLID_TRIGGER;
-       missile.movetype = MOVETYPE_FLYMISSILE;
-       missile.projectiledeathtype = DEATH_MONSTER_KNIGHT_FBALL;
-       setsize(missile, '-6 -6 -6', '6 6 6');          
-       setorigin(missile, self.origin + self.view_ofs + v_forward * 14);
-       missile.flags = FL_PROJECTILE;
-       missile.velocity = dir * 400;
-       missile.avelocity = '300 300 300';
-       missile.nextthink = time + 5;
-       missile.think = knight_fireball_explode;
-       missile.enemy = self.enemy;
-       missile.touch = knight_fireball_touch;
-       CSQCProjectile(missile, TRUE, PROJECTILE_FIREMINE, TRUE);
-}
-
-void knight_spike_explode()
-{
-       if(self)
-       {
-               pointparticles(particleeffectnum("TE_WIZSPIKE"), self.origin, '0 0 0', 1);
-               
-               RadiusDamage (self, self.realowner, autocvar_g_monster_knight_spike_damage, autocvar_g_monster_knight_spike_edgedamage, autocvar_g_monster_knight_spike_force, world, autocvar_g_monster_knight_spike_radius, DEATH_MONSTER_KNIGHT_SPIKE, other);
-               remove(self);
-       }
-}
-
-void knight_spike_touch()
-{
-       PROJECTILE_TOUCH;
-       
-       knight_spike_explode();
-}
-
-void knight_spike()
-{
-       entity missile;
-       vector dir = normalize((self.enemy.origin + '0 0 10') - self.origin);
-
-       self.effects |= EF_MUZZLEFLASH;
-
-       missile = spawn ();
-       missile.owner = missile.realowner = self;
-       missile.solid = SOLID_TRIGGER;
-       missile.movetype = MOVETYPE_FLYMISSILE;
-       setsize (missile, '0 0 0', '0 0 0');            
-       setorigin(missile, self.origin + '0 0 10' + v_forward * 14);
-       missile.scale = self.scale;
-       missile.flags = FL_PROJECTILE;
-       missile.velocity = dir * 400;
-       missile.avelocity = '300 300 300';
-       missile.nextthink = time + 5;
-       missile.think = knight_spike_explode;
-       missile.enemy = self.enemy;
-       missile.touch = knight_spike_touch;
-       CSQCProjectile(missile, TRUE, PROJECTILE_CRYLINK, TRUE);
-}
-
-void knight_spikes()
-{
-       self.knight_cycles += 1;
-       knight_spike();
-       
-       if(self.knight_cycles <= 7)
-               defer(0.1, knight_spikes);
-}
-
-float knight_attack_ranged()
-{
-       if not(self.flags & FL_ONGROUND)
-               return FALSE;
-               
-       self.knight_cycles = 0;
-       
-       RandomSelection_Init();
-       RandomSelection_Add(world, 1, "", autocvar_g_monster_knight_fireball_chance, 1);
-       RandomSelection_Add(world, 2, "", autocvar_g_monster_knight_inferno_chance, 1);
-       RandomSelection_Add(world, 3, "", autocvar_g_monster_knight_spike_chance, 1);
-       if(self.health >= 100) RandomSelection_Add(world, 4, "", ((vlen(self.enemy.origin - self.origin) > autocvar_g_monster_knight_jump_dist) ? 1 : autocvar_g_monster_knight_jump_chance), 1);
-       
-       switch(RandomSelection_chosen_float)
-       {
-               case 1:
-               {
-                       monsters_setframe(knight_anim_magic2);
-                       self.attack_finished_single = time + 2;
-                       defer(0.4, knight_fireball);
-                       
-                       return TRUE;
-               }
-               case 2:
-               {
-                       self.attack_finished_single = time + 3;
-                       defer(0.5, knight_inferno);
-                       return TRUE;
-               }
-               case 3:
-               {
-                       monsters_setframe(knight_anim_magic3);
-                       self.attack_finished_single = time + 3;
-                       defer(0.4, knight_spikes);
-                       
-                       return TRUE;
-               }
-               case 4:
-               {
-                       float er = vlen(self.enemy.origin - self.origin);
-                       
-                       if(er >= 400 && er < 1200)
-                       if(findtrajectorywithleading(self.origin, self.mins, self.maxs, self.enemy, 1000, 0, 10, 0, self))
-                       {
-                               self.velocity = findtrajectory_velocity;
-                               Damage(self.enemy, self, self, autocvar_g_monster_knight_jump_damage * monster_skill, DEATH_MONSTER_KNIGHT_CRUSH, self.enemy.origin, normalize(self.enemy.origin - self.origin));
-                               self.attack_finished_single = time + 2;
-                               return TRUE;
-                       }
-                       return FALSE;
-               }
-       }
-       
-       return FALSE;
-}
-
-float knight_attack(float attack_type)
-{
-       switch(attack_type)
-       {
-               case MONSTER_ATTACK_MELEE:
-               {
-                       float anim;
-                       if(random() < 0.3)
-                               anim = knight_anim_slice;
-                       else if(random() < 0.6)
-                               anim = knight_anim_smash;
-                       else
-                               anim = knight_anim_wattack;
-                       
-                       monsters_setframe(anim);
-                       self.attack_finished_single = time + 0.7;
-                       monster_melee(self.enemy, autocvar_g_monster_knight_melee_damage, 0.3, DEATH_MONSTER_KNIGHT_MELEE, TRUE);
-                       
-                       return TRUE;
-               }
-               case MONSTER_ATTACK_RANGED:
-               {
-                       if(knight_attack_ranged())
-                               return TRUE;
-               }
-       }
-       
-       return FALSE;
-}
-
-void knight_die()
-{
-       float chance = random();
-       Monster_CheckDropCvars ("knight");
-       
-       self.think = monster_dead_think;
-       self.nextthink = time + self.ticrate;
-       self.ltime = time + 5;
-       monsters_setframe((random() > 0.5) ? knight_anim_death1 : knight_anim_death2);
-       
-       if(chance < 0.10 || self.flags & MONSTERFLAG_MINIBOSS)
-       if(self.candrop)
-       {
-               self.superweapons_finished = time + autocvar_g_balance_superweapons_time + 5; // give the player a few seconds to find the weapon
-               self.weapon = WEP_FIREBALL;
-       }
-               
-       monster_hook_death(); // for post-death mods
-}
-
-void knight_spawn()
-{
-       if not(self.health)
-               self.health = autocvar_g_monster_knight_health;
-
-       self.damageforcescale   = 0.003;
-       self.classname                  = "monster_knight";
-       self.monster_attackfunc = knight_attack;
-       self.nextthink                  = time + random() * 0.5 + 0.1;
-       self.think                              = knight_think;
-       
-       monsters_setframe(knight_anim_stand);
-       
-       monster_setupsounds("knight");
-       
-       monster_hook_spawn(); // for post-spawn mods
-}
-
-void spawnfunc_monster_knight()
-{
-       if not(autocvar_g_monster_knight) { remove(self); return; }
-       
-       self.monster_spawnfunc = spawnfunc_monster_knight;
-       
-       if(Monster_CheckAppearFlags(self))
-               return;
-       
-       self.scale = 1.3;
-       
-       if not (monster_initialize(
-                        "Knight", MONSTER_KNIGHT,
-                        KNIGHT_MIN, KNIGHT_MAX,
-                        FALSE,
-                        knight_die, knight_spawn))
-       {
-               remove(self);
-               return;
-       }
-}
-
-// compatibility with old spawns
-void spawnfunc_monster_hell_knight() { spawnfunc_monster_knight(); }
-
-#endif // SVQC
diff --git a/qcsrc/server/monsters/monster/mage.qc b/qcsrc/server/monsters/monster/mage.qc
deleted file mode 100644 (file)
index d30c208..0000000
+++ /dev/null
@@ -1,431 +0,0 @@
-const vector MAGE_MIN = '-36 -36 -24';
-const vector MAGE_MAX = '36 36 50';
-
-string MAGE_MODEL = "models/monsters/mage.dpm";
-
-#ifdef SVQC
-float autocvar_g_monster_mage;
-float autocvar_g_monster_mage_health;
-float autocvar_g_monster_mage_speed;
-float autocvar_g_monster_mage_attack_spike_damage;
-float autocvar_g_monster_mage_attack_spike_radius;
-float autocvar_g_monster_mage_attack_spike_delay;
-float autocvar_g_monster_mage_attack_melee_damage;
-float autocvar_g_monster_mage_attack_melee_delay;
-float autocvar_g_monster_mage_heal_self;
-float autocvar_g_monster_mage_heal_friends;
-float autocvar_g_monster_mage_heal_minhealth;
-float autocvar_g_monster_mage_heal_range;
-float autocvar_g_monster_mage_heal_delay;
-float autocvar_g_monster_mage_shield_time;
-float autocvar_g_monster_mage_shield_delay;
-float autocvar_g_monster_mage_shield_blockpercent;
-float autocvar_g_monster_mage_attack_grenade_damage;
-float autocvar_g_monster_mage_attack_grenade_edgedamage;
-float autocvar_g_monster_mage_attack_grenade_radius;
-float autocvar_g_monster_mage_attack_grenade_lifetime;
-float autocvar_g_monster_mage_attack_grenade_force;
-float autocvar_g_monster_mage_attack_grenade_chance;
-
-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() mage_shield_die;
-
-float friend_needshelp(entity e)
-{
-       if(e == world)
-               return FALSE;
-       if(e.health <= 0)
-               return FALSE;
-       if(vlen(e.origin - self.origin) > autocvar_g_monster_mage_heal_range)
-               return FALSE;
-       if(IsDifferentTeam(e, self))
-               return FALSE;
-       if(e.frozen)
-               return FALSE;
-       if(!IS_PLAYER(e))
-               return (e.health < e.max_health);
-       if(e.items & IT_INVINCIBLE)
-               return FALSE;
-
-       switch(self.skin)
-       {
-               case 0:
-               {
-                       if(e.health < autocvar_g_balance_health_regenstable)
-                               return TRUE;
-                       break;
-               }
-               case 1:
-               {
-                       if((e.ammo_cells && e.ammo_cells < g_pickup_cells_max) || (e.ammo_rockets && e.ammo_rockets < g_pickup_rockets_max) || (e.ammo_nails && e.ammo_nails < g_pickup_nails_max) || (e.ammo_shells && e.ammo_shells < g_pickup_shells_max))
-                               return TRUE;
-                       break;
-               }
-               case 2:
-               {
-                       if(e.armorvalue < autocvar_g_balance_armor_regenstable)
-                               return TRUE;
-                       break;
-               }
-               case 3:
-               {
-                       if(e.health > 0)
-                               return TRUE;
-                       break;
-               }
-       }
-       
-       return FALSE;
-}
-
-void mage_think()
-{
-       entity head;
-       float need_help = FALSE;
-       
-       FOR_EACH_PLAYER(head)
-       if(friend_needshelp(head))
-       {
-               need_help = TRUE;
-               break; // found 1 player near us who is low on health
-       }
-       if(!need_help)
-       FOR_EACH_MONSTER(head)
-       if(head != self)
-       if(friend_needshelp(head))
-       {
-               need_help = TRUE;
-               break; // found 1 player near us who is low on health
-       }
-       
-       self.think = mage_think;
-       self.nextthink = time + self.ticrate;
-       
-       if(self.weaponentity)
-       if(time >= self.weaponentity.ltime)
-               mage_shield_die();
-               
-       if(self.health < autocvar_g_monster_mage_heal_minhealth || need_help)
-       if(time >= self.attack_finished_single)
-       if(random() < 0.5)
-               mage_heal();
-               
-       if(self.enemy)
-       if(self.health < self.max_health)
-       if(time >= self.lastshielded)
-       if(random() < 0.5)
-               mage_shield();
-       
-       monster_move(autocvar_g_monster_mage_speed, autocvar_g_monster_mage_speed, 50, mage_anim_walk, mage_anim_run, mage_anim_idle);
-}
-
-void mageattack_melee()
-{
-       monster_melee(self.enemy, autocvar_g_monster_mage_attack_melee_damage, 0.3, DEATH_MONSTER_MAGE, TRUE);
-}
-
-void mage_grenade_explode()
-{
-       pointparticles(particleeffectnum("explosion_small"), self.origin, '0 0 0', 1);
-       
-       sound(self, CH_SHOTS, "weapons/grenade_impact.wav", VOL_BASE, ATTN_NORM);
-       RadiusDamage (self, self.realowner, autocvar_g_monster_mage_attack_grenade_damage, autocvar_g_monster_mage_attack_grenade_edgedamage, autocvar_g_monster_mage_attack_grenade_radius, world, autocvar_g_monster_mage_attack_grenade_force, DEATH_MONSTER_MAGE, other);
-       remove(self);
-}
-
-void mage_grenade_touch()
-{
-       if(IS_PLAYER(other))
-       {
-               PROJECTILE_TOUCH;
-               mage_grenade_explode();
-               return;
-       }
-}
-
-void mage_throw_itemgrenade()
-{
-       makevectors(self.angles);
-
-       W_SetupShot_ProjectileSize (self, '-64 -64 -64', '64 64 64', FALSE, 4, "", CH_WEAPON_A, autocvar_g_monster_mage_attack_grenade_damage);
-       w_shotdir = v_forward; // no TrueAim for grenades please
-
-       entity gren = spawn ();
-       gren.owner = gren.realowner = self;
-       gren.classname = "grenade";
-       gren.bot_dodge = FALSE;
-       gren.movetype = MOVETYPE_BOUNCE;
-       gren.solid = SOLID_TRIGGER;
-       gren.projectiledeathtype = DEATH_MONSTER_MAGE;
-       setorigin(gren, w_shotorg);
-       setsize(gren, '-64 -64 -64', '64 64 64');
-
-       gren.nextthink = time + autocvar_g_monster_mage_attack_grenade_lifetime;
-       gren.think = mage_grenade_explode;
-       gren.use = mage_grenade_explode;
-       gren.touch = mage_grenade_touch;
-
-       gren.missile_flags = MIF_SPLASH | MIF_ARC;
-       W_SETUPPROJECTILEVELOCITY_UP(gren, g_monster_mage_attack_grenade);
-       
-       gren.flags = FL_PROJECTILE;
-       
-       setmodel(gren, "models/items/g_h50.md3");
-       
-       self.attack_finished_single = time + 1.5;
-}
-
-void mage_spike_explode()
-{
-       self.event_damage = func_null;
-
-       pointparticles(particleeffectnum("explosion_small"), self.origin, '0 0 0', 1);
-       RadiusDamage (self, self.realowner, autocvar_g_monster_mage_attack_spike_damage, autocvar_g_monster_mage_attack_spike_damage * 0.5, autocvar_g_monster_mage_attack_spike_radius, world, 0, DEATH_MONSTER_MAGE, other);
-
-       remove (self);
-}
-
-void mage_spike_touch()
-{
-       PROJECTILE_TOUCH;
-
-       mage_spike_explode();
-}
-
-void mage_spike_think()
-{
-       if(self.enemy.health <= 0 || self.owner.health <= 0 || time >= self.ltime)
-       {
-               mage_spike_explode();
-               return;
-       }
-       
-       vector dir = normalize((self.enemy.origin + '0 0 10') - self.origin);
-       
-       UpdateCSQCProjectile(self);
-       
-       if (monster_skill == 3)
-               self.velocity = dir * 350;
-       else
-               self.velocity = dir * 250;
-               
-       self.nextthink = time + 0.2;
-       self.think = mage_spike_think;  
-}
-
-void mage_spike()
-{
-       entity missile;
-       vector dir = normalize((self.enemy.origin + '0 0 10') - self.origin);
-
-       makevectors(self.angles);
-
-       missile = spawn ();
-       missile.owner = missile.realowner = self;
-       missile.think = mage_spike_think;
-       missile.ltime = time + 7;
-       missile.nextthink = time;
-       missile.solid = SOLID_BBOX;
-       missile.movetype = MOVETYPE_FLYMISSILE;
-       missile.flags = FL_PROJECTILE;
-       setorigin(missile, self.origin + v_forward * 14 + '0 0 30' + v_right * -14);
-       setsize (missile, '0 0 0', '0 0 0');    
-       missile.velocity = dir * 400;
-       missile.avelocity = '300 300 300';
-       missile.enemy = self.enemy;
-       missile.touch = mage_spike_touch;
-       
-       CSQCProjectile(missile, TRUE, PROJECTILE_MAGE_SPIKE, TRUE);
-}
-
-void mage_heal()
-{
-       entity head;
-       float washealed = FALSE;
-       
-       for(head = world; (head = findfloat(head, monster_attack, TRUE)); ) if(friend_needshelp(head))
-       {
-               washealed = TRUE;
-               string fx = "";
-               if(IS_PLAYER(head))
-               {
-                       switch(self.skin)
-                       {
-                               case 0:
-                                       if(head.health < autocvar_g_balance_health_regenstable) head.health = bound(0, head.health + autocvar_g_monster_mage_heal_friends, autocvar_g_balance_health_regenstable);
-                                       fx = "healing_fx";
-                                       break;
-                               case 1:
-                                       if(head.ammo_cells) head.ammo_cells = bound(head.ammo_cells, head.ammo_cells + 1, g_pickup_cells_max);
-                                       if(head.ammo_rockets) head.ammo_rockets = bound(head.ammo_rockets, head.ammo_rockets + 1, g_pickup_rockets_max);
-                                       if(head.ammo_shells) head.ammo_shells = bound(head.ammo_shells, head.ammo_shells + 2, g_pickup_shells_max);
-                                       if(head.ammo_nails) head.ammo_nails = bound(head.ammo_nails, head.ammo_nails + 5, g_pickup_nails_max);
-                                       fx = "ammoregen_fx";
-                                       break;
-                               case 2:
-                                       if(head.armorvalue < autocvar_g_balance_armor_regenstable)
-                                       {
-                                               head.armorvalue = bound(0, head.armorvalue + autocvar_g_monster_mage_heal_friends, autocvar_g_balance_armor_regenstable);
-                                               fx = "armorrepair_fx";
-                                       }
-                                       break;
-                               case 3:
-                                       head.health = bound(0, head.health - ((head == self)  ? autocvar_g_monster_mage_heal_self : autocvar_g_monster_mage_heal_friends), autocvar_g_balance_health_regenstable);
-                                       fx = "rage";
-                                       break;
-                       }
-                       
-                       pointparticles(particleeffectnum(fx), head.origin, '0 0 0', 1);
-               }
-               else
-               {
-                       pointparticles(particleeffectnum("healing_fx"), head.origin, '0 0 0', 1);
-                       head.health = bound(0, head.health + autocvar_g_monster_mage_heal_friends, head.max_health);
-                       WaypointSprite_UpdateHealth(head.sprite, head.health);
-               }
-       }
-       
-       if(washealed)
-       {
-               monsters_setframe(mage_anim_attack);
-               self.attack_finished_single = time + autocvar_g_monster_mage_heal_delay;
-       }
-}
-
-void mage_shield_die()
-{
-       if not(self.weaponentity)
-               return; // why would this be called without a shield?
-       
-       self.armorvalue = 1;
-       
-       remove(self.weaponentity);
-       
-       self.weaponentity = world;
-}
-
-void mage_shield()
-{
-       if(self.weaponentity)
-               return; // already have a shield
-               
-       entity shield = spawn();
-
-       shield.owner = self;
-       shield.team = self.team;
-       shield.ltime = time + autocvar_g_monster_mage_shield_time;
-       shield.health = 70;
-       shield.classname = "shield";
-       shield.effects = EF_ADDITIVE;
-       shield.movetype = MOVETYPE_NOCLIP;
-       shield.solid = SOLID_TRIGGER;
-       shield.avelocity = '7 0 11';
-       shield.scale = self.scale * 0.6;
-       
-       setattachment(shield, self, "");
-       setmodel(shield, "models/ctf/shield.md3");
-       setsize(shield, shield.scale * shield.mins, shield.scale * shield.maxs);
-       
-       self.weaponentity = shield;
-       
-       self.lastshielded = time + autocvar_g_monster_mage_shield_delay;
-       
-       monsters_setframe(mage_anim_attack);
-       self.attack_finished_single = time + 1;
-       
-       self.armorvalue = autocvar_g_monster_mage_shield_blockpercent / 100;
-}
-
-float mage_attack(float attack_type)
-{
-       switch(attack_type)
-       {
-               case MONSTER_ATTACK_MELEE:
-               {
-                       monsters_setframe(mage_anim_attack);
-                       self.attack_finished_single = time + autocvar_g_monster_mage_attack_melee_delay;
-                       defer(0.2, mageattack_melee);
-                       
-                       return TRUE;
-               }
-               case MONSTER_ATTACK_RANGED:
-               {
-                       if(random() < autocvar_g_monster_mage_attack_grenade_chance / 100)
-                       {
-                               mage_throw_itemgrenade();
-                               return TRUE;
-                       }
-       
-                       monsters_setframe(mage_anim_attack);
-                       self.attack_finished_single = time + autocvar_g_monster_mage_attack_spike_delay;
-                       defer(0.2, mage_spike);
-                       
-                       return TRUE;
-               }
-       }
-       
-       return FALSE;
-}
-
-void mage_die()
-{
-       Monster_CheckDropCvars ("mage");
-       
-       self.think = monster_dead_think;
-       self.nextthink = time + self.ticrate;
-       self.ltime = time + 5;
-       monsters_setframe(mage_anim_death);
-       
-       monster_hook_death(); // for post-death mods
-}
-
-void mage_spawn()
-{
-       if not(self.health)
-               self.health = autocvar_g_monster_mage_health;
-
-       self.damageforcescale   = 0.003;
-       self.classname                  = "monster_mage";
-       self.monster_attackfunc = mage_attack;
-       self.nextthink                  = time + random() * 0.5 + 0.1;
-       self.think                              = mage_think;
-       
-       monsters_setframe(mage_anim_walk);
-       
-       monster_setupsounds("mage");
-       
-       monster_hook_spawn(); // for post-spawn mods
-}
-
-void spawnfunc_monster_mage()
-{
-       if not(autocvar_g_monster_mage) { remove(self); return; }
-       
-       self.monster_spawnfunc = spawnfunc_monster_mage;
-       
-       if(Monster_CheckAppearFlags(self))
-               return;
-       
-       if not (monster_initialize(
-                        "Mage", MONSTER_MAGE,
-                        MAGE_MIN, MAGE_MAX,
-                        FALSE,
-                        mage_die, mage_spawn))
-       {
-               remove(self);
-               return;
-       }
-}
-
-// compatibility with old spawns
-void spawnfunc_monster_shalrath() { spawnfunc_monster_mage(); }
-
-#endif // SVQC
diff --git a/qcsrc/server/monsters/monster/shambler.qc b/qcsrc/server/monsters/monster/shambler.qc
deleted file mode 100644 (file)
index ec78221..0000000
+++ /dev/null
@@ -1,159 +0,0 @@
-const vector SHAMBLER_MIN = '-41 -41 -31';
-const vector SHAMBLER_MAX = '41 41 65';
-
-string SHAMBLER_MODEL = "models/monsters/shambler.mdl";
-
-#ifdef SVQC
-float autocvar_g_monster_shambler;
-float autocvar_g_monster_shambler_health;
-float autocvar_g_monster_shambler_damage;
-float autocvar_g_monster_shambler_attack_lightning_damage;
-float autocvar_g_monster_shambler_attack_claw_damage;
-float autocvar_g_monster_shambler_speed_walk;
-float autocvar_g_monster_shambler_speed_run;
-
-const float shambler_anim_stand        = 0;
-const float shambler_anim_walk                 = 1;
-const float shambler_anim_run          = 2;
-const float shambler_anim_smash        = 3;
-const float shambler_anim_swingr       = 4;
-const float shambler_anim_swingl       = 5;
-const float shambler_anim_magic        = 6;
-const float shambler_anim_pain                 = 7;
-const float shambler_anim_death        = 8;
-
-void shambler_think()
-{
-       self.think = shambler_think;
-       self.nextthink = time + self.ticrate;
-       
-       monster_move(autocvar_g_monster_shambler_speed_run, autocvar_g_monster_shambler_speed_walk, 300, shambler_anim_run, shambler_anim_walk, shambler_anim_stand);
-}
-
-void shambler_smash()
-{
-       monster_melee(self.enemy, autocvar_g_monster_shambler_damage, 0.3, DEATH_MONSTER_SHAMBLER_SMASH, TRUE);
-}
-
-void shambler_delayedsmash()
-{
-       monsters_setframe(shambler_anim_smash);
-       defer(0.7, shambler_smash);
-       self.attack_finished_single = time + 1.1;
-}
-
-void shambler_swing()
-{
-       float r = (random() < 0.5);
-       monsters_setframe((r) ? shambler_anim_swingr : shambler_anim_swingl);
-       monster_melee(self.enemy, autocvar_g_monster_shambler_attack_claw_damage, 0.3, DEATH_MONSTER_SHAMBLER_CLAW, TRUE);
-       self.attack_finished_single = time + 0.8;
-       if(r)
-               defer(0.5, shambler_swing);
-}
-
-void CastLightning()
-{
-       local vector org, dir;
-       //vector v = '0 0 0';
-
-       self.effects |= EF_MUZZLEFLASH;
-
-       org = self.origin + '0 0 40';
-
-       dir = self.enemy.origin + '0 0 16' - org;
-       dir = normalize (dir);
-
-       traceline (org, self.origin + dir * 1000, TRUE, self);
-               
-       FireRailgunBullet (org, org + dir * 1000, autocvar_g_monster_shambler_attack_lightning_damage * monster_skill, 0, 0, 0, 0, 0, DEATH_MONSTER_SHAMBLER_ZAP);
-       
-       // teamcolor / hit beam effect
-       //v = WarpZone_UnTransformOrigin(WarpZone_trace_transform, trace_endpos);
-       //WarpZone_TrailParticles(world, particleeffectnum("TE_TEI_G3"), org, v);
-       
-       te_csqc_lightningarc(org, trace_endpos);
-}
-
-float shambler_attack(float attack_type)
-{
-       switch(attack_type)
-       {
-               case MONSTER_ATTACK_MELEE:
-               {
-                       float chance = random();
-
-                       if(chance > 0.6)
-                               shambler_delayedsmash();
-                       else
-                               shambler_swing();
-                       
-                       return TRUE;
-               }
-               case MONSTER_ATTACK_RANGED:
-               {
-                       monsters_setframe(shambler_anim_magic);
-                       self.attack_finished_single = time + 1.1;
-                       defer(0.6, CastLightning);
-                       
-                       return TRUE;
-               }
-       }
-       
-       return FALSE;
-}
-
-void shambler_die()
-{
-       Monster_CheckDropCvars ("shambler");
-       
-       self.think = monster_dead_think;
-       self.nextthink = time + self.ticrate;
-       self.ltime = time + 5;
-       monsters_setframe(shambler_anim_death);
-       
-       monster_hook_death(); // for post-death mods
-}
-
-void shambler_spawn()
-{
-       if not(self.health)
-               self.health = autocvar_g_monster_shambler_health;
-
-       self.damageforcescale   = 0.003;
-       self.classname                  = "monster_shambler";
-       self.monster_attackfunc = shambler_attack;
-       self.nextthink                  = time + random() * 0.5 + 0.1;
-       self.think                              = shambler_think;
-       self.weapon                             = WEP_NEX;
-       
-       monsters_setframe(shambler_anim_stand);
-       
-       monster_setupsounds("shambler");
-       
-       monster_hook_spawn(); // for post-spawn mods
-}
-
-void spawnfunc_monster_shambler()
-{
-       if not(autocvar_g_monster_shambler) { remove(self); return; }
-       
-       self.monster_spawnfunc = spawnfunc_monster_shambler;
-       
-       if(Monster_CheckAppearFlags(self))
-               return;
-       
-       self.scale = 1.3;
-       
-       if not (monster_initialize(
-                        "Shambler", MONSTER_SHAMBLER,
-                        SHAMBLER_MIN, SHAMBLER_MAX,
-                        FALSE,
-                        shambler_die, shambler_spawn))
-       {
-               remove(self);
-               return;
-       }
-}
-
-#endif // SVQC
diff --git a/qcsrc/server/monsters/monster/slime.qc b/qcsrc/server/monsters/monster/slime.qc
deleted file mode 100644 (file)
index 8f73cea..0000000
+++ /dev/null
@@ -1,141 +0,0 @@
-const vector SLIME_MIN = '-16 -16 -24';
-const vector SLIME_MAX = '16 16 16';
-
-string SLIME_MODEL = "models/monsters/slime.dpm";
-
-#ifdef SVQC
-float autocvar_g_monster_slime;
-float autocvar_g_monster_slime_health;
-float autocvar_g_monster_slime_speed_walk;
-float autocvar_g_monster_slime_speed_run;
-
-const float slime_anim_walk            = 0;
-const float slime_anim_idle            = 1;
-const float slime_anim_jump            = 2;
-const float slime_anim_fly             = 3;
-const float slime_anim_die             = 4;
-const float slime_anim_pain            = 5;
-
-void slime_think()
-{
-       self.think = slime_think;
-       self.nextthink = time + self.ticrate;
-       
-       monster_move(autocvar_g_monster_slime_speed_run, autocvar_g_monster_slime_speed_walk, 20, slime_anim_walk, slime_anim_walk, slime_anim_idle);
-}
-
-void slime_touch_jump()
-{
-       if(self.health > 0)
-       if(other.health > 0)
-       if(other.takedamage)
-       if(vlen(self.velocity) > 200)
-       {
-               Damage (self, world, world, self.health + self.max_health + 200, DEATH_MONSTER_SLIME, self.origin, '0 0 0');
-                       
-               return;
-       }
-
-       if(trace_dphitcontents)
-       {
-               self.touch = MonsterTouch;
-               self.movetype = MOVETYPE_WALK;
-       }
-}
-
-float slime_attack(float attack_type)
-{
-       switch(attack_type)
-       {
-               case MONSTER_ATTACK_MELEE:
-               case MONSTER_ATTACK_RANGED:
-               {
-                       makevectors(self.angles);
-                       if(monster_leap(slime_anim_jump, slime_touch_jump, v_forward * 600 + '0 0 200', 0.5))
-                               return TRUE;
-               }
-       }
-       
-       return FALSE;
-}
-
-void slime_explode()
-{
-       RadiusDamage(self, self, 250 * monster_skill, 15, 250 * (monster_skill * 0.7), world, 250, DEATH_MONSTER_SLIME, world);
-       pointparticles(particleeffectnum("explosion_medium"), self.origin, '0 0 0', 1);
-       sound(self, CH_SHOTS, "weapons/rocket_impact.wav", VOL_BASE, ATTN_NORM);
-       
-       setmodel(self, "");
-}
-
-void slime_dead()
-{
-       self.health = -100; // gibbed
-       slime_explode();
-       
-       Monster_CheckDropCvars ("slime"); // drop items after exploding to prevent player picking up item before dying
-       
-       self.deadflag = DEAD_DEAD;
-       self.think = Monster_Fade;
-       self.nextthink = time + 0.1;
-       
-       monster_hook_death();
-       
-       self.event_damage = func_null; // reset by monster_hook_death
-       self.takedamage = DAMAGE_NO;
-}
-
-void slime_die()
-{
-       self.think                      = slime_dead;
-       self.nextthink          = time;
-       self.event_damage   = func_null;
-       self.movetype           = MOVETYPE_NONE;
-       self.enemy                      = world;
-       self.health                     = 0;
-       
-       self.SendFlags |= MSF_MOVE | MSF_STATUS;
-}
-
-void slime_spawn()
-{
-       if not(self.health)
-               self.health = autocvar_g_monster_slime_health;
-       
-       self.damageforcescale   = 0.003;
-       self.classname                  = "monster_slime";
-       self.monster_attackfunc = slime_attack;
-       self.nextthink                  = time + random() * 0.5 + 0.1;
-       self.think                              = slime_think;
-       
-       monsters_setframe(slime_anim_idle);
-       
-       monster_setupsounds("slime");
-       
-       monster_hook_spawn(); // for post-spawn mods
-}
-
-void spawnfunc_monster_slime()
-{
-       if not(autocvar_g_monster_slime) { remove(self); return; }
-       
-       self.monster_spawnfunc = spawnfunc_monster_slime;
-       
-       if(Monster_CheckAppearFlags(self))
-               return;
-       
-       if not (monster_initialize(
-                        "Slime", MONSTER_SLIME,
-                        SLIME_MIN, SLIME_MAX,
-                        FALSE,
-                        slime_die, slime_spawn))
-       {
-               remove(self);
-               return;
-       }
-}
-
-// compatibility with old spawns
-void spawnfunc_monster_tarbaby() { spawnfunc_monster_slime(); }
-
-#endif // SVQC
diff --git a/qcsrc/server/monsters/monster/spider.qc b/qcsrc/server/monsters/monster/spider.qc
deleted file mode 100644 (file)
index b72a857..0000000
+++ /dev/null
@@ -1,217 +0,0 @@
-const vector SPIDER_MIN = '-18 -18 -25';
-const vector SPIDER_MAX = '18 18 30';
-
-string SPIDER_MODEL = "models/monsters/spider.dpm";
-
-#ifdef SVQC
-float autocvar_g_monster_spider;
-float autocvar_g_monster_spider_stopspeed;
-float autocvar_g_monster_spider_attack_leap_delay;
-float autocvar_g_monster_spider_attack_stand_damage;
-float autocvar_g_monster_spider_attack_stand_delay;
-float autocvar_g_monster_spider_attack_fire_time;
-float autocvar_g_monster_spider_health;
-float autocvar_g_monster_spider_speed_walk;
-float autocvar_g_monster_spider_speed_run;
-float autocvar_g_monster_spider_attack_type;
-
-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_type; // used to switch between fire & ice attacks
-const float SPIDER_TYPE_ICE            = 0;
-const float SPIDER_TYPE_FIRE   = 1;
-
-void spider_think()
-{
-       self.think = spider_think;
-       self.nextthink = time + self.ticrate;
-       
-       monster_move(autocvar_g_monster_spider_speed_run, autocvar_g_monster_spider_speed_walk, autocvar_g_monster_spider_stopspeed, spider_anim_walk, spider_anim_walk, spider_anim_idle);
-}
-
-void spider_web_explode()
-{
-       entity e;
-       if(self)
-       {
-               float damg = 0, edamg = 0, rad = 1;
-               switch(self.realowner.spider_type)
-               {
-                       case SPIDER_TYPE_ICE:
-                               rad = 25;
-                               pointparticles(particleeffectnum("electro_impact"), self.origin, '0 0 0', 1);
-                               break;
-                       case SPIDER_TYPE_FIRE:
-                               pointparticles(particleeffectnum("fireball_explode"), self.origin, '0 0 0', 1);
-                               damg = 15;
-                               rad = 25;
-                               edamg = 6;
-                               break;
-               }
-               
-               RadiusDamage(self, self.realowner, damg, edamg, 0, world, rad, DEATH_MONSTER_SPIDER_FIRE, world); // ice deals no damage anyway
-               
-               for(e = findradius(self.origin, rad); e; e = e.chain) if(e.takedamage && e.deadflag == DEAD_NO) if(e.health > 0)
-               {
-                       switch(self.realowner.spider_type)
-                       {
-                               case SPIDER_TYPE_ICE:
-                                       Freeze(e, 0.3, 2, FALSE);
-                                       break;
-                               case SPIDER_TYPE_FIRE:
-                                       Fire_AddDamage(e, self.realowner, 5 * monster_skill, autocvar_g_monster_spider_attack_fire_time, DEATH_MONSTER_SPIDER_FIRE);
-                                       break;
-                       }
-               }
-               
-               remove(self);
-       }
-}
-
-void spider_web_touch()
-{
-       PROJECTILE_TOUCH;
-       
-       spider_web_explode();
-}
-
-void spider_shootweb(float ptype)
-{
-       float p = 0;
-       string snd = "";
-       switch(ptype)
-       {
-               case SPIDER_TYPE_ICE:
-                       p = PROJECTILE_ELECTRO;
-                       snd = "weapons/electro_fire2.wav";
-                       break;
-               case SPIDER_TYPE_FIRE:
-                       p = PROJECTILE_FIREMINE;
-                       snd = "weapons/fireball_fire.wav";
-                       break;
-       }
-       
-       vector fmins = '-4 -4 -4', fmaxs = '4 4 4';
-
-       W_SetupShot_ProjectileSize(self, fmins, fmaxs, FALSE, 2, snd, CH_WEAPON_A, 0);
-
-       w_shotdir = v_forward; // no TrueAim for grenades please
-
-       entity proj = spawn ();
-       proj.classname = "plasma";
-       proj.owner = proj.realowner = self;
-       proj.use = spider_web_touch;
-       proj.think = adaptor_think2use_hittype_splash;
-       proj.bot_dodge = TRUE;
-       proj.bot_dodgerating = 0;
-       proj.nextthink = time + 5;
-       PROJECTILE_MAKETRIGGER(proj);
-       proj.projectiledeathtype = DEATH_MONSTER_SPIDER_FIRE;
-       setorigin(proj, w_shotorg);
-
-       //proj.glow_size = 50;
-       //proj.glow_color = 45;
-       proj.movetype = MOVETYPE_BOUNCE;
-       W_SETUPPROJECTILEVELOCITY_UP(proj, g_monster_spider_attack_web);
-       proj.touch = spider_web_touch;
-       setsize(proj, fmins, fmaxs);
-       proj.takedamage = DAMAGE_NO;
-       proj.damageforcescale = 0;
-       proj.health = 500;
-       proj.event_damage = func_null;
-       proj.flags = FL_PROJECTILE;
-       proj.damagedbycontents = TRUE;
-
-       proj.bouncefactor = 0.3;
-       proj.bouncestop = 0.05;
-       proj.missile_flags = MIF_SPLASH | MIF_ARC;
-
-       CSQCProjectile(proj, TRUE, p, TRUE);
-}
-
-float spider_attack(float attack_type)
-{
-       switch(attack_type)
-       {
-               case MONSTER_ATTACK_MELEE:
-               {
-                       monster_melee(self.enemy, autocvar_g_monster_spider_attack_stand_damage, 0.3, DEATH_MONSTER_SPIDER, TRUE);
-                       monsters_setframe((random() > 0.5) ? spider_anim_attack : spider_anim_attack2);
-                       self.attack_finished_single = time + autocvar_g_monster_spider_attack_stand_delay;
-                       
-                       return TRUE;
-               }
-               case MONSTER_ATTACK_RANGED:
-               {
-                       if(self.enemy.frozen)
-                               return FALSE;
-                       
-                       monsters_setframe(spider_anim_attack2);
-                       self.attack_finished_single = time + autocvar_g_monster_spider_attack_leap_delay;
-                       monster_makevectors(self.enemy);
-                       spider_shootweb(self.spider_type);
-                       
-                       return TRUE;
-               }
-       }
-       
-       return FALSE;
-}
-
-void spider_die()
-{
-       Monster_CheckDropCvars ("spider");
-       
-       self.think = monster_dead_think;
-       self.nextthink = time + self.ticrate;
-       self.ltime = time + 5;
-       monsters_setframe(spider_anim_attack);
-       self.angles += '180 0 0';
-       
-       monster_hook_death(); // for post-death mods
-}
-
-void spider_spawn() 
-{
-       if not(self.health)
-               self.health = autocvar_g_monster_spider_health;
-       
-       self.classname                  = "monster_spider";
-       self.nextthink                  = time + random() * 0.5 + 0.1;
-       self.monster_attackfunc = spider_attack;
-       self.think                              = spider_think;
-       
-       monsters_setframe(spider_anim_idle);
-       
-       monster_setupsounds("spider");
-       
-       if not(self.spider_type)
-               self.spider_type = autocvar_g_monster_spider_attack_type;
-       
-       monster_hook_spawn(); // for post-spawn mods
-}
-
-void spawnfunc_monster_spider() 
-{
-       if not(autocvar_g_monster_spider) { remove(self); return; }
-       
-       self.monster_spawnfunc = spawnfunc_monster_spider;
-       
-       if(Monster_CheckAppearFlags(self))
-               return;
-       
-       if not (monster_initialize(
-                        "Spider", MONSTER_SPIDER,
-                        SPIDER_MIN, SPIDER_MAX,
-                        FALSE,
-                        spider_die, spider_spawn))
-       {
-               remove(self);
-               return;
-       }
-}
-
-#endif // SVQC
diff --git a/qcsrc/server/monsters/monster/stingray.qc b/qcsrc/server/monsters/monster/stingray.qc
deleted file mode 100644 (file)
index 61dccfb..0000000
+++ /dev/null
@@ -1,95 +0,0 @@
-const vector STINGRAY_MIN = '-20 -20 -31';
-const vector STINGRAY_MAX = '20 20 20';
-
-string STINGRAY_MODEL = "models/monsters/fish.mdl";
-
-#ifdef SVQC
-float autocvar_g_monster_stingray;
-float autocvar_g_monster_stingray_health;
-float autocvar_g_monster_stingray_damage;
-float autocvar_g_monster_stingray_speed_walk;
-float autocvar_g_monster_stingray_speed_run;
-
-const float stingray_anim_attack = 0;
-const float stingray_anim_death  = 1;
-const float stingray_anim_swim   = 2;
-const float stingray_anim_pain   = 3;
-
-void stingray_think()
-{
-       self.think = stingray_think;
-       self.nextthink = time + self.ticrate;
-       
-       monster_move(autocvar_g_monster_stingray_speed_run, autocvar_g_monster_stingray_speed_walk, 10, stingray_anim_swim, stingray_anim_swim, stingray_anim_swim);
-}
-
-float stingray_attack(float attack_type)
-{
-       switch(attack_type)
-       {
-               case MONSTER_ATTACK_MELEE:
-               {
-                       monsters_setframe(stingray_anim_attack);
-                       self.attack_finished_single = time + 0.5;
-                       monster_melee(self.enemy, autocvar_g_monster_stingray_damage, 0.1, DEATH_MONSTER_STINGRAY, FALSE);
-                       
-                       return TRUE;
-               }
-               case MONSTER_ATTACK_RANGED:
-       }
-       
-       return FALSE;
-}
-
-void stingray_die()
-{
-       Monster_CheckDropCvars ("stingray");
-       
-       self.think = monster_dead_think;
-       self.nextthink = time + self.ticrate;
-       self.ltime = time + 5;
-       monsters_setframe(stingray_anim_death);
-       
-       monster_hook_death(); // for post-death mods
-}
-
-void stingray_spawn()
-{
-       if not(self.health)
-               self.health = autocvar_g_monster_stingray_health;
-
-       self.damageforcescale   = 0.5;
-       self.classname                  = "monster_stingray";
-       self.monster_attackfunc = stingray_attack;
-       self.flags                         |= FL_SWIM;
-       self.nextthink                  = time + random() * 0.5 + 0.1;
-       self.think                              = stingray_think;
-       
-       monster_setupsounds("stingray");
-       
-       monster_hook_spawn(); // for post-spawn mods
-}
-
-void spawnfunc_monster_stingray()
-{
-       if not(autocvar_g_monster_stingray) { remove(self); return; }
-       
-       self.monster_spawnfunc = spawnfunc_monster_stingray;
-       
-       if(Monster_CheckAppearFlags(self))
-               return;
-       
-       self.scale = 1.3;
-       
-       if not (monster_initialize(
-                        "Stingray", MONSTER_STINGRAY,
-                        STINGRAY_MIN, STINGRAY_MAX,
-                        TRUE,
-                        stingray_die, stingray_spawn))
-       {
-               remove(self);
-               return;
-       }
-}
-
-#endif // SVQC
diff --git a/qcsrc/server/monsters/monster/wyvern.qc b/qcsrc/server/monsters/monster/wyvern.qc
deleted file mode 100644 (file)
index e9eda6d..0000000
+++ /dev/null
@@ -1,154 +0,0 @@
-const vector WYVERN_MIN = '-20 -20 -58';
-const vector WYVERN_MAX = '20 20 20';
-
-string WYVERN_MODEL = "models/monsters/wizard.mdl";
-
-#ifdef SVQC
-float autocvar_g_monster_wyvern;
-float autocvar_g_monster_wyvern_health;
-float autocvar_g_monster_wyvern_speed_walk;
-float autocvar_g_monster_wyvern_speed_run;
-float autocvar_g_monster_wyvern_fireball_damage;
-float autocvar_g_monster_wyvern_fireball_force;
-float autocvar_g_monster_wyvern_fireball_radius;
-float autocvar_g_monster_wyvern_fireball_edgedamage;
-float autocvar_g_monster_wyvern_fireball_damagetime;
-float autocvar_g_monster_wyvern_fireball_speed;
-
-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_think()
-{
-       self.think = wyvern_think;
-       self.nextthink = time + self.ticrate;
-       
-       monster_move(autocvar_g_monster_wyvern_speed_run, autocvar_g_monster_wyvern_speed_walk, 300, wyvern_anim_fly, wyvern_anim_hover, wyvern_anim_hover);
-}
-
-void wyvern_fireball_explode()
-{
-       entity e;
-       if(self)
-       {
-               pointparticles(particleeffectnum("fireball_explode"), self.origin, '0 0 0', 1);
-               
-               RadiusDamage(self, self.realowner, autocvar_g_monster_wyvern_fireball_damage, autocvar_g_monster_wyvern_fireball_edgedamage, autocvar_g_monster_wyvern_fireball_force, world, autocvar_g_monster_wyvern_fireball_radius, self.projectiledeathtype, world);
-               
-               for(e = world; (e = findfloat(e, takedamage, DAMAGE_AIM)); ) if(vlen(e.origin - self.origin) <= autocvar_g_monster_wyvern_fireball_radius)
-                       Fire_AddDamage(e, self, 5 * monster_skill, autocvar_g_monster_wyvern_fireball_damagetime, self.projectiledeathtype);
-               
-               remove(self);
-       }
-}
-
-void wyvern_fireball_touch()
-{
-       PROJECTILE_TOUCH;
-       
-       wyvern_fireball_explode();
-}
-
-void wyvern_fireball()
-{
-       entity missile = spawn();
-       vector dir = normalize((self.enemy.origin + '0 0 10') - self.origin);
-       
-       monster_makevectors(self.enemy);
-
-       missile.owner = missile.realowner = self;
-       missile.solid = SOLID_TRIGGER;
-       missile.movetype = MOVETYPE_FLYMISSILE;
-       missile.projectiledeathtype = DEATH_MONSTER_WYVERN;
-       setsize(missile, '-6 -6 -6', '6 6 6');          
-       setorigin(missile, self.origin + self.view_ofs + v_forward * 14);
-       missile.flags = FL_PROJECTILE;
-       missile.velocity = dir * autocvar_g_monster_wyvern_fireball_speed;
-       missile.avelocity = '300 300 300';
-       missile.nextthink = time + 5;
-       missile.think = wyvern_fireball_explode;
-       missile.enemy = self.enemy;
-       missile.touch = wyvern_fireball_touch;
-       CSQCProjectile(missile, TRUE, PROJECTILE_FIREMINE, TRUE);
-}
-
-float wyvern_attack(float attack_type)
-{
-       switch(attack_type)
-       {
-               case MONSTER_ATTACK_MELEE:
-               case MONSTER_ATTACK_RANGED:
-               {
-                       self.attack_finished_single = time + 1.2;
-                       
-                       wyvern_fireball();
-                       
-                       return TRUE;
-               }
-       }
-       
-       return FALSE;
-}
-
-void wyvern_die()
-{
-       Monster_CheckDropCvars ("wyvern");
-       
-       self.think                      = monster_dead_think;
-       self.nextthink          = time + self.ticrate;
-       self.ltime                      = time + 5;
-       self.velocity_x         = -200 + 400 * random();
-       self.velocity_y         = -200 + 400 * random();
-       self.velocity_z         = 100 + 100 * random();
-       
-       monsters_setframe(wyvern_anim_death);
-       
-       monster_hook_death(); // for post-death mods
-}
-
-void wyvern_spawn()
-{
-       if not(self.health)
-               self.health = autocvar_g_monster_wyvern_health;
-       
-       self.classname                  = "monster_wyvern";
-       self.monster_attackfunc = wyvern_attack;
-       self.nextthink                  = time + random() * 0.5 + 0.1;
-       self.movetype                   = MOVETYPE_FLY;
-       self.flags                         |= FL_FLY;
-       self.think                              = wyvern_think;
-       
-       monster_setupsounds("wyvern");
-       
-       monster_hook_spawn(); // for post-spawn mods
-}
-
-void spawnfunc_monster_wyvern()
-{
-       if not(autocvar_g_monster_wyvern) { remove(self); return; }
-       
-       self.monster_spawnfunc = spawnfunc_monster_wyvern;
-       
-       if(Monster_CheckAppearFlags(self))
-               return;
-       
-       self.scale = 1.3;
-       
-       if not (monster_initialize(
-                        "Wyvern", MONSTER_WYVERN,
-                        WYVERN_MIN, WYVERN_MAX,
-                        TRUE,
-                        wyvern_die, wyvern_spawn))
-       {
-               remove(self);
-               return;
-       }
-}
-
-// compatibility with old spawns
-void spawnfunc_monster_wizard() { spawnfunc_monster_wyvern(); }
-
-#endif // SVQC
diff --git a/qcsrc/server/monsters/monster/zombie.qc b/qcsrc/server/monsters/monster/zombie.qc
deleted file mode 100644 (file)
index aa96920..0000000
+++ /dev/null
@@ -1,165 +0,0 @@
-const vector ZOMBIE_MIN = '-18 -18 -25';
-const vector ZOMBIE_MAX = '18 18 47';
-
-string ZOMBIE_MODEL = "models/monsters/zombie.dpm";
-
-#ifdef SVQC
-float autocvar_g_monster_zombie;
-float autocvar_g_monster_zombie_stopspeed;
-float autocvar_g_monster_zombie_attack_leap_damage;
-float autocvar_g_monster_zombie_attack_leap_delay;
-float autocvar_g_monster_zombie_attack_leap_force;
-float autocvar_g_monster_zombie_attack_leap_speed;
-float autocvar_g_monster_zombie_attack_stand_damage;
-float autocvar_g_monster_zombie_attack_stand_delay;
-float autocvar_g_monster_zombie_health;
-float autocvar_g_monster_zombie_speed_walk;
-float autocvar_g_monster_zombie_speed_run;
-
-const float zombie_anim_attackleap                     = 0;
-const float zombie_anim_attackrun1                     = 1;
-const float zombie_anim_attackrun2                     = 2;
-const float zombie_anim_attackrun3                     = 3;
-const float zombie_anim_attackstanding1                = 4;
-const float zombie_anim_attackstanding2                = 5;
-const float zombie_anim_attackstanding3                = 6;
-const float zombie_anim_blockend                       = 7;
-const float zombie_anim_blockstart                     = 8;
-const float zombie_anim_deathback1                     = 9;
-const float zombie_anim_deathback2                     = 10;
-const float zombie_anim_deathback3                     = 11;
-const float zombie_anim_deathfront1                    = 12;
-const float zombie_anim_deathfront2                    = 13;
-const float zombie_anim_deathfront3                    = 14;
-const float zombie_anim_deathleft1                     = 15;
-const float zombie_anim_deathleft2                     = 16;
-const float zombie_anim_deathright1                    = 17;
-const float zombie_anim_deathright2                    = 18;
-const float zombie_anim_idle                           = 19;
-const float zombie_anim_painback1                      = 20;
-const float zombie_anim_painback2                      = 21;
-const float zombie_anim_painfront1                     = 22;
-const float zombie_anim_painfront2                     = 23;
-const float zombie_anim_runbackwards           = 24;
-const float zombie_anim_runbackwardsleft       = 25;
-const float zombie_anim_runbackwardsright      = 26;
-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_think()
-{
-       self.think = zombie_think;
-       self.nextthink = time + self.ticrate;
-
-       monster_move(autocvar_g_monster_zombie_speed_run, autocvar_g_monster_zombie_speed_walk, autocvar_g_monster_zombie_stopspeed, zombie_anim_runforward, zombie_anim_runforward, zombie_anim_idle);
-}
-
-void zombie_attack_leap_touch()
-{
-       if (self.health <= 0)
-               return;
-               
-       vector angles_face;
-
-       if(other.takedamage)
-       {
-               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_skill, DEATH_MONSTER_ZOMBIE_JUMP, other.origin, angles_face);
-               self.touch = MonsterTouch; // instantly turn it off to stop damage spam
-       }
-
-       if (trace_dphitcontents)
-               self.touch = MonsterTouch;
-}
-
-float zombie_attack(float attack_type)
-{
-       switch(attack_type)
-       {
-               case MONSTER_ATTACK_MELEE:
-               {
-                       float rand = random(), chosen_anim;
-               
-                       if(rand < 0.33)
-                               chosen_anim = zombie_anim_attackstanding1;
-                       else if(rand < 0.66)
-                               chosen_anim = zombie_anim_attackstanding2;
-                       else
-                               chosen_anim = zombie_anim_attackstanding3;
-                               
-                       monsters_setframe(chosen_anim);
-
-                       self.attack_finished_single = time + autocvar_g_monster_zombie_attack_stand_delay;
-                       
-                       monster_melee(self.enemy, autocvar_g_monster_zombie_attack_stand_damage, 0.3, DEATH_MONSTER_ZOMBIE_MELEE, TRUE);
-                       
-                       return TRUE;
-               }
-               case MONSTER_ATTACK_RANGED:
-               {
-                       makevectors(self.angles);
-                       if(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 TRUE;
-               }
-       }
-       
-       return FALSE;
-}
-
-void zombie_die()
-{
-       Monster_CheckDropCvars ("zombie");
-       
-       self.think = monster_dead_think;
-       self.nextthink = time + self.ticrate;
-       self.ltime = time + 5;
-       monsters_setframe((random() > 0.5) ? zombie_anim_deathback1 : zombie_anim_deathfront1);
-               
-       monster_hook_death(); // for post-death mods
-}
-
-void zombie_spawn() 
-{
-       if not(self.health)
-               self.health = autocvar_g_monster_zombie_health;
-       
-       self.classname                  = "monster_zombie";
-       self.spawn_time                 = time + 2.1;
-       self.nextthink                  = time + random() * 0.5 + 0.1;
-       self.think                              = zombie_think;
-       self.monster_attackfunc = zombie_attack;
-       self.spawnshieldtime    = self.spawn_time;
-       self.respawntime                = 0.1;
-       self.spawnflags            |= MONSTER_RESPAWN_DEATHPOINT; // always enabled for zombie
-       
-       monsters_setframe(zombie_anim_spawn);
-       
-       monster_setupsounds("zombie");
-       
-       monster_hook_spawn(); // for post-spawn mods
-}
-
-void spawnfunc_monster_zombie() 
-{
-       if not(autocvar_g_monster_zombie) { remove(self); return; }
-       
-       self.monster_spawnfunc = spawnfunc_monster_zombie;
-       
-       if(Monster_CheckAppearFlags(self))
-               return;
-       
-       if not (monster_initialize(
-                        "Zombie", MONSTER_ZOMBIE,
-                        ZOMBIE_MIN, ZOMBIE_MAX,
-                        FALSE,
-                        zombie_die, zombie_spawn))
-       {
-               remove(self);
-               return;
-       }
-}
-
-#endif //SVQC
diff --git a/qcsrc/server/monsters/monsters.qh b/qcsrc/server/monsters/monsters.qh
deleted file mode 100644 (file)
index 53b5a13..0000000
+++ /dev/null
@@ -1,20 +0,0 @@
-// Lib
-#ifdef SVQC
-#include "lib/defs.qh"
-#include "lib/monsters.qc"
-#include "lib/spawn.qc"
-#endif
-
-// Monsters
-#include "monster/brute.qc"
-#include "monster/animus.qc"
-#include "monster/shambler.qc"
-#include "monster/bruiser.qc"
-#include "monster/wyvern.qc"
-#include "monster/cerberus.qc"
-#include "monster/slime.qc"
-#include "monster/knight.qc"
-#include "monster/stingray.qc"
-#include "monster/mage.qc"
-#include "monster/zombie.qc"
-#include "monster/spider.qc"
index 7b82f8127854c0384c7ab86882ca1f71667bfed2..47af8e78d78ab609a0e976cdbca6b0564674b871 100644 (file)
@@ -8,15 +8,15 @@ void invasion_spawnpoint()
 float invasion_PickMonster(float have_shamblers)
 {
        if(autocvar_g_invasion_zombies_only)
-               return MONSTER_ZOMBIE;
+               return MON_ZOMBIE;
 
        float i;
        
        RandomSelection_Init();
        
-       for(i = MONSTER_FIRST + 1; i < MONSTER_LAST; ++i)
+       for(i = MON_FIRST; i <= MON_LAST; ++i)
        {
-               if(i == MONSTER_STINGRAY || i == MONSTER_WYVERN || (i == MONSTER_SHAMBLER && have_shamblers >= 1))
+               if(i == MON_STINGRAY || i == MON_WYVERN || (i == MON_SHAMBLER && have_shamblers >= 1))
                        continue; // flying/swimming monsters not yet supported
                
                RandomSelection_Add(world, i, "", 1, 1);
@@ -88,7 +88,7 @@ float Invasion_CheckWinner()
        
        FOR_EACH_MONSTER(head) if(head.health > 0)
        {
-               if(head.monsterid == MONSTER_SHAMBLER)
+               if(head.monsterid == MON_SHAMBLER)
                        ++shamblers;
                ++total_alive_monsters;
        }
index d438faa62a48526031470b3b422787bbb027344a..5fae3a0e1d51712e6e85cf16020e02564d8e9908 100644 (file)
@@ -119,7 +119,7 @@ MUTATOR_HOOKFUNCTION(minstagib_MonsterLoot)
 MUTATOR_HOOKFUNCTION(minstagib_MonsterSpawn)
 {
        // always refill ammo
-       if(self.monsterid == MONSTER_MAGE)
+       if(self.monsterid == MON_MAGE)
                self.skin = 1;
        
        return FALSE;
index d446d1bd3cf68b4a8df4427694c18b8dc3e3b292..7de6565c3f987b83c914eb7fa32c4d6723cb603f 100644 (file)
@@ -26,6 +26,7 @@ sys-post.qh
 ../common/command/shared_defs.qh
 ../common/net_notice.qh
 ../common/animdecide.qh
+../common/monsters/monsters.qh
 
 autocvars.qh
 constants.qh
@@ -53,7 +54,7 @@ mutators/mutator_nades.qh
 tturrets/include/turrets_early.qh
 vehicles/vehicles_def.qh
 
-monsters/lib/monsters_early.qh
+../common/monsters/lib/monsters_early.qh
 
 generator.qh
 
@@ -233,7 +234,7 @@ round_handler.qc
 
 ../common/explosion_equation.qc
 
-monsters/monsters.qh
+../common/monsters/monsters.qc
 
 mutators/base.qc
 mutators/gamemode_assault.qc