]> git.xonotic.org Git - xonotic/xonotic-data.pk3dir.git/commitdiff
Merge branch 'master' into Mario/monsters
authorMario <mario.mario@y7mail.com>
Fri, 30 Aug 2013 11:04:40 +0000 (21:04 +1000)
committerMario <mario.mario@y7mail.com>
Fri, 30 Aug 2013 11:04:40 +0000 (21:04 +1000)
38 files changed:
1  2 
defaultXonotic.cfg
qcsrc/client/Main.qc
qcsrc/client/View.qc
qcsrc/client/projectile.qc
qcsrc/client/scoreboard.qc
qcsrc/client/waypointsprites.qc
qcsrc/common/constants.qh
qcsrc/common/deathtypes.qh
qcsrc/common/mapinfo.qh
qcsrc/common/monsters/monster/brute.qc
qcsrc/common/monsters/monster/knight.qc
qcsrc/common/monsters/monster/mage.qc
qcsrc/common/monsters/monster/slime.qc
qcsrc/common/monsters/monster/spider.qc
qcsrc/common/monsters/monster/zombie.qc
qcsrc/common/monsters/sv_monsters.qc
qcsrc/common/notifications.qh
qcsrc/server/cl_client.qc
qcsrc/server/cl_physics.qc
qcsrc/server/cl_player.qc
qcsrc/server/cl_weapons.qc
qcsrc/server/cl_weaponsystem.qc
qcsrc/server/defs.qh
qcsrc/server/g_damage.qc
qcsrc/server/g_world.qc
qcsrc/server/generator.qc
qcsrc/server/miscfunctions.qc
qcsrc/server/mutators/gamemode_ctf.qc
qcsrc/server/mutators/gamemode_onslaught.qc
qcsrc/server/mutators/mutator_dodging.qc
qcsrc/server/mutators/mutator_minstagib.qc
qcsrc/server/sv_main.qc
qcsrc/server/t_items.qc
qcsrc/server/teamplay.qc
qcsrc/server/tturrets/system/system_damage.qc
qcsrc/server/tturrets/system/system_main.qc
qcsrc/server/vehicles/vehicles.qc
qcsrc/server/w_electro.qc

Simple merge
Simple merge
Simple merge
Simple merge
Simple merge
Simple merge
index 74f3da07933f06e5fe5b6b076a09010f18694058,b3dafaaf7ba7293813715bb1d1ae0aa673af8f14..385d3b2e123715baee2e654415b66ae1c86ffe11
@@@ -182,9 -180,9 +182,12 @@@ const float STAT_SECRETS_FOUND = 71
  const float STAT_RESPAWN_TIME = 72;
  const float STAT_ROUNDSTARTTIME = 73;
  
- const float STAT_MONSTERS_TOTAL = 73;
- const float STAT_MONSTERS_KILLED = 74;
+ const float STAT_WEAPONS2 = 74;
+ const float STAT_WEAPONS3 = 75;
++const float STAT_MONSTERS_TOTAL = 76;
++const float STAT_MONSTERS_KILLED = 77;
 +
  // mod stats (1xx)
  const float STAT_REDALIVE = 100;
  const float STAT_BLUEALIVE = 101;
@@@ -326,60 -324,58 +329,60 @@@ const float ATTEN_MAX = 3.984375
  #define VOL_BASEVOICE 1.0
  
  // this sets sounds and other properties of the projectiles in csqc
- float PROJECTILE_ELECTRO = 1;
- float PROJECTILE_ROCKET = 2;
- float PROJECTILE_TAG = 3;
- float PROJECTILE_BULLET = 4;
- float PROJECTILE_CRYLINK = 5;
- float PROJECTILE_ELECTRO_BEAM = 6;
- float PROJECTILE_GRENADE = 7;
- float PROJECTILE_GRENADE_BOUNCING = 8;
- float PROJECTILE_MINE = 9;
- float PROJECTILE_LASER = 10;
- float PROJECTILE_HLAC = 11;
- float PROJECTILE_SEEKER = 12;
- float PROJECTILE_FLAC = 13;
- float PROJECTILE_PORTO_RED = 14;
- float PROJECTILE_PORTO_BLUE = 15;
- float PROJECTILE_HOOKBOMB = 16;
- float PROJECTILE_HAGAR = 17;
- float PROJECTILE_HAGAR_BOUNCING = 18;
- float PROJECTILE_BULLET_GLOWING = 19;
- float PROJECTILE_CRYLINK_BOUNCING = 20;
- float PROJECTILE_FIREBALL = 21;
- float PROJECTILE_FIREMINE = 22;
- float PROJECTILE_BULLET_GLOWING_TRACER = 23;
- float PROJECTILE_RAPTORCANNON   = 24;
- float PROJECTILE_RAPTORBOMB     = 25;
- float PROJECTILE_RAPTORBOMBLET  = 26;
- float PROJECTILE_SPIDERROCKET   = 27;
- float PROJECTILE_WAKIROCKET     = 28;
- float PROJECTILE_WAKICANNON     = 29;
- float PROJECTILE_BUMBLE_GUN     = 30;
- float PROJECTILE_BUMBLE_BEAM    = 31;
- float PROJECTILE_MAGE_SPIKE           = 32;
- float PROJECTILE_NADE_RED             = 50;
- float PROJECTILE_NADE_RED_BURN        = 51;
- float PROJECTILE_NADE_BLUE            = 52;
- float PROJECTILE_NADE_BLUE_BURN       = 53;
- float PROJECTILE_NADE_YELLOW          = 54;
- float PROJECTILE_NADE_YELLOW_BURN     = 55;
- float PROJECTILE_NADE_PINK            = 56;
- float PROJECTILE_NADE_PINK_BURN       = 57;
- float PROJECTILE_NADE                         = 58;
- float PROJECTILE_NADE_BURN                    = 59;
- float SPECIES_HUMAN        =  0;
- float SPECIES_ROBOT_SOLID  =  1;
- float SPECIES_ALIEN        =  2;
- float SPECIES_ANIMAL       =  3;
- float SPECIES_ROBOT_RUSTY  =  4;
- float SPECIES_ROBOT_SHINY  =  5;
- float SPECIES_RESERVED     = 15;
+ const float PROJECTILE_ELECTRO = 1;
+ const float PROJECTILE_ROCKET = 2;
+ const float PROJECTILE_TAG = 3;
+ const float PROJECTILE_BULLET = 4;
+ const float PROJECTILE_CRYLINK = 5;
+ const float PROJECTILE_ELECTRO_BEAM = 6;
+ const float PROJECTILE_GRENADE = 7;
+ const float PROJECTILE_GRENADE_BOUNCING = 8;
+ const float PROJECTILE_MINE = 9;
+ const float PROJECTILE_LASER = 10;
+ const float PROJECTILE_HLAC = 11;
+ const float PROJECTILE_SEEKER = 12;
+ const float PROJECTILE_FLAC = 13;
+ const float PROJECTILE_PORTO_RED = 14;
+ const float PROJECTILE_PORTO_BLUE = 15;
+ const float PROJECTILE_HOOKBOMB = 16;
+ const float PROJECTILE_HAGAR = 17;
+ const float PROJECTILE_HAGAR_BOUNCING = 18;
+ const float PROJECTILE_BULLET_GLOWING = 19;
+ const float PROJECTILE_CRYLINK_BOUNCING = 20;
+ const float PROJECTILE_FIREBALL = 21;
+ const float PROJECTILE_FIREMINE = 22;
+ const float PROJECTILE_BULLET_GLOWING_TRACER = 23;
+ const float PROJECTILE_RAPTORCANNON = 24;
+ const float PROJECTILE_RAPTORBOMB = 25;
+ const float PROJECTILE_RAPTORBOMBLET = 26;
+ const float PROJECTILE_SPIDERROCKET = 27;
+ const float PROJECTILE_WAKIROCKET = 28;
+ const float PROJECTILE_WAKICANNON = 29;
+ const float PROJECTILE_BUMBLE_GUN = 30;
+ const float PROJECTILE_BUMBLE_BEAM = 31;
++float PROJECTILE_MAGE_SPIKE = 32;
++
+ const float PROJECTILE_NADE_RED = 50;
+ const float PROJECTILE_NADE_RED_BURN = 51;
+ const float PROJECTILE_NADE_BLUE = 52;
+ const float PROJECTILE_NADE_BLUE_BURN = 53;
+ const float PROJECTILE_NADE_YELLOW = 54;
+ const float PROJECTILE_NADE_YELLOW_BURN = 55;
+ const float PROJECTILE_NADE_PINK = 56;
+ const float PROJECTILE_NADE_PINK_BURN = 57;
+ const float PROJECTILE_NADE = 58;
+ const float PROJECTILE_NADE_BURN = 59;
+ const float SPECIES_HUMAN = 0;
+ const float SPECIES_ROBOT_SOLID = 1;
+ const float SPECIES_ALIEN = 2;
+ const float SPECIES_ANIMAL = 3;
+ const float SPECIES_ROBOT_RUSTY = 4;
+ const float SPECIES_ROBOT_SHINY = 5;
+ const float SPECIES_RESERVED = 15;
  
  #define FRAGS_PLAYER 0
  #define FRAGS_SPECTATOR -666
Simple merge
index f59fd2c2431bd8d2dde012dcb998210337c8a4fb,db08fdf07c6a71116099cfa384d325ba19230ac6..d8b84709b3739951025f801b070a8384f3d23e7b
@@@ -78,16 -78,13 +78,16 @@@ REGISTER_GAMETYPE(_("Freeze Tag"),ft,g_
  REGISTER_GAMETYPE(_("Keepaway"),ka,g_keepaway,KEEPAWAY,"timelimit=20 pointlimit=30")
  #define g_keepaway IS_GAMETYPE(KEEPAWAY)
  
- float MAPINFO_FEATURE_WEAPONS       = 1; // not defined for minstagib-only maps
- float MAPINFO_FEATURE_VEHICLES      = 2;
- float MAPINFO_FEATURE_TURRETS       = 4;
 +REGISTER_GAMETYPE(_("Invasion"),invasion,g_invasion,INVASION,"pointlimit=5")
 +#define g_invasion IS_GAMETYPE(INVASION)
 +
+ const float MAPINFO_FEATURE_WEAPONS       = 1; // not defined for minstagib-only maps
+ const float MAPINFO_FEATURE_VEHICLES      = 2;
+ const float MAPINFO_FEATURE_TURRETS       = 4;
  
- float MAPINFO_FLAG_HIDDEN           = 1; // not in lsmaps/menu/vcall/etc., can just be changed to manually
- float MAPINFO_FLAG_FORBIDDEN        = 2; // don't even allow the map by a cvar setting that allows hidden maps
- float MAPINFO_FLAG_FRUSTRATING      = 4; // this map is near impossible to play, enable at your own risk
const float MAPINFO_FLAG_HIDDEN           = 1; // not in lsmaps/menu/vcall/etc., can just be changed to manually
const float MAPINFO_FLAG_FORBIDDEN        = 2; // don't even allow the map by a cvar setting that allows hidden maps
const float MAPINFO_FLAG_FRUSTRATING      = 4; // this map is near impossible to play, enable at your own risk
  
  float MapInfo_count;
  
index 2f1d912c1728e8316acceb3c13714ea4b7627b1a,0000000000000000000000000000000000000000..a78813beea413037e4bfe69f60771be7b379e11b
mode 100644,000000..100644
--- /dev/null
@@@ -1,269 -1,0 +1,269 @@@
-       sound(self, CH_WEAPON_A, "weapons/uzi_fire.wav", VOL_BASE, ATTN_NORM);
 +#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")
 +);
 +
 +#define BRUTE_SETTINGS(monster) \
 +      MON_ADD_CVAR(monster, health) \
 +      MON_ADD_CVAR(monster, attack_chainsaw_damage) \
 +      MON_ADD_CVAR(monster, attack_uzi_bullets) \
 +      MON_ADD_CVAR(monster, attack_uzi_damage) \
 +      MON_ADD_CVAR(monster, attack_uzi_force) \
 +      MON_ADD_CVAR(monster, attack_uzi_chance) \
 +      MON_ADD_CVAR(monster, attack_grenade_damage) \
 +      MON_ADD_CVAR(monster, attack_grenade_edgedamage) \
 +      MON_ADD_CVAR(monster, attack_grenade_force) \
 +      MON_ADD_CVAR(monster, attack_grenade_radius) \
 +      MON_ADD_CVAR(monster, attack_grenade_speed) \
 +      MON_ADD_CVAR(monster, attack_grenade_speed_up) \
 +      MON_ADD_CVAR(monster, speed_stop) \
 +      MON_ADD_CVAR(monster, speed_run) \
 +      MON_ADD_CVAR(monster, speed_walk) 
 +
 +#ifdef SVQC
 +BRUTE_SETTINGS(brute)
 +#endif // SVQC
 +#else
 +#ifdef SVQC
 +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, MON_CVAR(brute, attack_chainsaw_damage), self.attack_range, 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);
 +      
-       sound(self, CH_SHOTS, "weapons/grenade_impact.wav", VOL_BASE, ATTN_NORM);
++      sound(self, CH_WEAPON_A, "weapons/uzi_fire.wav", VOL_BASE, ATTEN_NORM);
 +      fireBallisticBullet(CENTER_OR_VIEWOFS(self), v_forward, 0.02, 18000, 5, MON_CVAR(brute, attack_uzi_damage), MON_CVAR(brute, attack_uzi_force), DEATH_MONSTER_BRUTE_UZI, 0, 1, 115);
 +      endFireBallisticBullet();
 +      
 +      if(self.brute_cycles <= MON_CVAR(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_WEAPON_A, "weapons/grenade_fire.wav", VOL_BASE, ATTN_NORM);
++      sound(self, CH_SHOTS, "weapons/grenade_impact.wav", VOL_BASE, ATTEN_NORM);
 +
 +      self.event_damage = func_null;
 +      self.takedamage = DAMAGE_NO;
 +
 +      if(self.movetype == MOVETYPE_NONE)
 +              self.velocity = self.oldvelocity;
 +
 +      RadiusDamage (self, self.realowner, MON_CVAR(brute, attack_grenade_damage), MON_CVAR(brute, attack_grenade_edgedamage), MON_CVAR(brute, attack_grenade_radius), world, MON_CVAR(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;
 +      
 +      monster_makevectors(self.enemy);
 +      
++      sound(self, CH_WEAPON_A, "weapons/grenade_fire.wav", VOL_BASE, ATTEN_NORM);
 +
 +      gren = spawn ();
 +      gren.owner = gren.realowner = self;
 +      gren.classname = "grenade";
 +      gren.bot_dodge = TRUE;
 +      gren.bot_dodgerating = MON_CVAR(brute, attack_grenade_damage);
 +      gren.movetype = MOVETYPE_BOUNCE;
 +      PROJECTILE_MAKETRIGGER(gren);
 +      gren.projectiledeathtype = DEATH_MONSTER_BRUTE_GRENADE;
 +      setorigin(gren, CENTER_OR_VIEWOFS(self));
 +      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 = 50;
 +      gren.damageforcescale = 0;
 +      gren.event_damage = brute_grenade_damage;
 +      gren.damagedbycontents = TRUE;
 +      gren.missile_flags = MIF_SPLASH | MIF_ARC;
 +      W_SetupProjectileVelocityEx(gren, v_forward, v_up, MON_CVAR(brute, attack_grenade_speed), MON_CVAR(brute, attack_grenade_speed_up), 0, 0, FALSE);
 +
 +      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() <= MON_CVAR(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()
 +{
 +      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; }
 +}
 +
 +// compatibility with old spawns
 +void spawnfunc_monster_ogre() { spawnfunc_monster_brute(); }
 +
 +float m_brute(float req)
 +{
 +      switch(req)
 +      {
 +              case MR_THINK:
 +              {
 +                      monster_move(MON_CVAR(brute, speed_run), MON_CVAR(brute, speed_walk), MON_CVAR(brute, speed_stop), 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 = MON_CVAR(brute, health);
 +                      
 +                      self.monster_loot = spawnfunc_item_bullets;
 +                      self.monster_attackfunc = brute_attack;
 +                      monsters_setframe(brute_anim_idle);
 +                      self.weapon = WEP_GRENADE_LAUNCHER;
 +                      
 +                      return TRUE;
 +              }
 +              case MR_INIT:
 +              {
 +                      // nothing
 +                      return TRUE;
 +              }
 +              case MR_CONFIG:
 +              {
 +                      MON_CONFIG_SETTINGS(BRUTE_SETTINGS(brute))
 +                      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
index 433a9d3ee828632495b0d48c7dee9e7fcecd79c0,0000000000000000000000000000000000000000..ca74c3c69e491db1393e224851c9ebd3c3f03678
mode 100644,000000..100644
--- /dev/null
@@@ -1,343 -1,0 +1,343 @@@
-       sound(self.enemy, CHAN_AUTO, "player/lava.wav", 1, ATTN_NORM);
 +#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")
 +);
 +
 +#define KNIGHT_SETTINGS(monster) \
 +      MON_ADD_CVAR(monster, health) \
 +      MON_ADD_CVAR(monster, attack_melee_damage) \
 +      MON_ADD_CVAR(monster, attack_inferno_damage) \
 +      MON_ADD_CVAR(monster, attack_inferno_damagetime) \
 +      MON_ADD_CVAR(monster, attack_inferno_chance) \
 +      MON_ADD_CVAR(monster, attack_fireball_damage) \
 +      MON_ADD_CVAR(monster, attack_fireball_edgedamage) \
 +      MON_ADD_CVAR(monster, attack_fireball_damagetime) \
 +      MON_ADD_CVAR(monster, attack_fireball_force) \
 +      MON_ADD_CVAR(monster, attack_fireball_radius) \
 +      MON_ADD_CVAR(monster, attack_fireball_chance) \
 +      MON_ADD_CVAR(monster, attack_spike_damage) \
 +      MON_ADD_CVAR(monster, attack_spike_edgedamage) \
 +      MON_ADD_CVAR(monster, attack_spike_force) \
 +      MON_ADD_CVAR(monster, attack_spike_radius) \
 +      MON_ADD_CVAR(monster, attack_spike_chance) \
 +      MON_ADD_CVAR(monster, attack_jump_damage) \
 +      MON_ADD_CVAR(monster, attack_jump_distance) \
 +      MON_ADD_CVAR(monster, attack_jump_chance) \
 +      MON_ADD_CVAR(monster, speed_stop) \
 +      MON_ADD_CVAR(monster, speed_run) \
 +      MON_ADD_CVAR(monster, speed_walk) 
 +
 +#ifdef SVQC
 +KNIGHT_SETTINGS(knight)
 +#endif // SVQC
 +#else
 +#ifdef SVQC
 +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, CHAN_WEAPON, "weapons/fireball2.wav", 1, ATTN_NORM);
++      sound(self.enemy, CHAN_AUTO, "player/lava.wav", 1, ATTEN_NORM);
 +      
 +      if(vlen(self.enemy.origin - self.origin) <= 2000)
 +              Fire_AddDamage(self.enemy, self, MON_CVAR(knight, attack_inferno_damage) * monster_skill, MON_CVAR(knight, attack_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, MON_CVAR(knight, attack_fireball_damage), MON_CVAR(knight, attack_fireball_edgedamage), MON_CVAR(knight, attack_fireball_force), world, MON_CVAR(knight, attack_fireball_radius), self.projectiledeathtype, world);
 +              
 +              for(e = world; (e = findfloat(e, takedamage, DAMAGE_AIM)); ) if(vlen(e.origin - self.origin) <= MON_CVAR(knight, attack_inferno_damage))
 +                      Fire_AddDamage(e, self, 5 * monster_skill, MON_CVAR(knight, attack_fireball_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, ATTEN_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, MON_CVAR(knight, attack_spike_damage), MON_CVAR(knight, attack_spike_edgedamage), MON_CVAR(knight, attack_spike_force), world, MON_CVAR(knight, attack_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, "", MON_CVAR(knight, attack_fireball_chance), 1);
 +      RandomSelection_Add(world, 2, "", MON_CVAR(knight, attack_inferno_chance), 1);
 +      RandomSelection_Add(world, 3, "", MON_CVAR(knight, attack_spike_chance), 1);
 +      if(self.health >= 100) RandomSelection_Add(world, 4, "", ((vlen(self.enemy.origin - self.origin) > MON_CVAR(knight, attack_jump_distance)) ? 1 : MON_CVAR(knight, attack_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, MON_CVAR(knight, attack_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, MON_CVAR(knight, attack_melee_damage), self.attack_range, DEATH_MONSTER_KNIGHT_MELEE, TRUE);
 +                      
 +                      return TRUE;
 +              }
 +              case MONSTER_ATTACK_RANGED:
 +              {
 +                      if(knight_attack_ranged())
 +                              return TRUE;
 +              }
 +      }
 +      
 +      return FALSE;
 +}
 +
 +void spawnfunc_monster_knight()
 +{
 +      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(MON_CVAR(knight, speed_run), MON_CVAR(knight, speed_walk), MON_CVAR(knight, speed_stop), 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.spawnflags & 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 = MON_CVAR(knight, health);
 +                      
 +                      self.monster_loot = spawnfunc_item_armor_big;
 +                      self.monster_attackfunc = knight_attack;
 +                      monsters_setframe(knight_anim_stand);
 +                      
 +                      return TRUE;
 +              }
 +              case MR_INIT:
 +              {
 +                      precache_sound ("player/lava.wav");
 +                      return TRUE;
 +              }
 +              case MR_CONFIG:
 +              {
 +                      MON_CONFIG_SETTINGS(KNIGHT_SETTINGS(knight))
 +                      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
index b729b381aac0f55fb22e44080c7e3b1806e4124c,0000000000000000000000000000000000000000..67dba01efac95c5355e621d81d816b7cdcbc4f6d
mode 100644,000000..100644
--- /dev/null
@@@ -1,490 -1,0 +1,490 @@@
-       sound(self, CH_SHOTS, "weapons/grenade_impact.wav", VOL_BASE, ATTN_NORM);
 +#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")
 +);
 +
 +#define MAGE_SETTINGS(monster) \
 +      MON_ADD_CVAR(monster, health) \
 +      MON_ADD_CVAR(monster, attack_spike_damage) \
 +      MON_ADD_CVAR(monster, attack_spike_radius) \
 +      MON_ADD_CVAR(monster, attack_spike_delay) \
 +      MON_ADD_CVAR(monster, attack_spike_accel) \
 +      MON_ADD_CVAR(monster, attack_spike_decel) \
 +      MON_ADD_CVAR(monster, attack_spike_turnrate) \
 +      MON_ADD_CVAR(monster, attack_spike_speed_max) \
 +      MON_ADD_CVAR(monster, attack_spike_smart) \
 +      MON_ADD_CVAR(monster, attack_spike_smart_trace_min) \
 +      MON_ADD_CVAR(monster, attack_spike_smart_trace_max) \
 +      MON_ADD_CVAR(monster, attack_spike_smart_mindist) \
 +      MON_ADD_CVAR(monster, attack_melee_damage) \
 +      MON_ADD_CVAR(monster, attack_melee_delay) \
 +      MON_ADD_CVAR(monster, attack_grenade_damage) \
 +      MON_ADD_CVAR(monster, attack_grenade_edgedamage) \
 +      MON_ADD_CVAR(monster, attack_grenade_force) \
 +      MON_ADD_CVAR(monster, attack_grenade_radius) \
 +      MON_ADD_CVAR(monster, attack_grenade_lifetime) \
 +      MON_ADD_CVAR(monster, attack_grenade_chance) \
 +      MON_ADD_CVAR(monster, attack_grenade_speed) \
 +      MON_ADD_CVAR(monster, attack_grenade_speed_up) \
 +      MON_ADD_CVAR(monster, heal_self) \
 +      MON_ADD_CVAR(monster, heal_allies) \
 +      MON_ADD_CVAR(monster, heal_minhealth) \
 +      MON_ADD_CVAR(monster, heal_range) \
 +      MON_ADD_CVAR(monster, heal_delay) \
 +      MON_ADD_CVAR(monster, shield_time) \
 +      MON_ADD_CVAR(monster, shield_delay) \
 +      MON_ADD_CVAR(monster, shield_blockpercent) \
 +      MON_ADD_CVAR(monster, speed_stop) \
 +      MON_ADD_CVAR(monster, speed_run) \
 +      MON_ADD_CVAR(monster, speed_walk) 
 +
 +#ifdef SVQC
 +MAGE_SETTINGS(mage)
 +#endif // SVQC
 +#else
 +#ifdef SVQC
 +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) > MON_CVAR(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: return (e.health < autocvar_g_balance_health_regenstable);
 +              case 1: return ((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));
 +              case 2: return (e.armorvalue < autocvar_g_balance_armor_regenstable);
 +              case 3: return (e.health > 0);
 +      }
 +      
 +      return FALSE;
 +}
 +
 +void mageattack_melee()
 +{
 +      monster_melee(self.enemy, MON_CVAR(mage, attack_melee_damage), self.attack_range, 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);
++      sound(self, CH_SHOTS, "weapons/grenade_impact.wav", VOL_BASE, ATTEN_NORM);
 +      RadiusDamage (self, self.realowner, MON_CVAR(mage, attack_grenade_damage), MON_CVAR(mage, attack_grenade_edgedamage), MON_CVAR(mage, attack_grenade_radius), world, MON_CVAR(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);
 +      
 +      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, CENTER_OR_VIEWOFS(self));
 +      setsize(gren, '-64 -64 -64', '64 64 64');
 +
 +      gren.nextthink = time + MON_CVAR(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_SetupProjectileVelocityEx(gren, v_forward, v_up, MON_CVAR(mage, attack_grenade_speed), MON_CVAR(mage, attack_grenade_speed_up), 0, 0, FALSE);
 +      
 +      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;
 +      
++      sound(self, CH_SHOTS, "weapons/grenade_impact.wav", VOL_BASE, ATTEN_NORM);
 +      
 +      pointparticles(particleeffectnum("explosion_small"), self.origin, '0 0 0', 1);
 +      RadiusDamage (self, self.realowner, MON_CVAR(mage, attack_spike_damage), MON_CVAR(mage, attack_spike_damage) * 0.5, MON_CVAR(mage, attack_spike_radius), world, 0, DEATH_MONSTER_MAGE, other);
 +
 +      remove (self);
 +}
 +
 +void mage_spike_touch()
 +{
 +      PROJECTILE_TOUCH;
 +
 +      mage_spike_explode();
 +}
 +
 +// copied from W_Seeker_Think
 +void mage_spike_think()
 +{
 +      entity e;
 +      vector desireddir, olddir, newdir, eorg;
 +      float turnrate;
 +      float dist;
 +      float spd;
 +
 +      if (time > self.ltime || self.enemy.health <= 0 || self.owner.health <= 0)
 +      {
 +              self.projectiledeathtype |= HITTYPE_SPLASH;
 +              mage_spike_explode();
 +      }
 +
 +      spd = vlen(self.velocity);
 +      spd = bound(
 +              spd - MON_CVAR(mage, attack_spike_decel) * frametime,
 +              MON_CVAR(mage, attack_spike_speed_max),
 +              spd + MON_CVAR(mage, attack_spike_accel) * frametime
 +      );
 +
 +      if (self.enemy != world)
 +              if (self.enemy.takedamage != DAMAGE_AIM || self.enemy.deadflag != DEAD_NO)
 +                      self.enemy = world;
 +
 +      if (self.enemy != world)
 +      {
 +              e               = self.enemy;
 +              eorg            = 0.5 * (e.absmin + e.absmax);
 +              turnrate        = MON_CVAR(mage, attack_spike_turnrate); // how fast to turn
 +              desireddir      = normalize(eorg - self.origin);
 +              olddir          = normalize(self.velocity); // get my current direction
 +              dist            = vlen(eorg - self.origin);
 +
 +              // Do evasive maneuvers for world objects? ( this should be a cpu hog. :P )
 +              if (MON_CVAR(mage, attack_spike_smart) && (dist > MON_CVAR(mage, attack_spike_smart_mindist)))
 +              {
 +                      // Is it a better idea (shorter distance) to trace to the target itself?
 +                      if ( vlen(self.origin + olddir * self.wait) < dist)
 +                              traceline(self.origin, self.origin + olddir * self.wait, FALSE, self);
 +                      else
 +                              traceline(self.origin, eorg, FALSE, self);
 +
 +                      // Setup adaptive tracelength
 +                      self.wait = bound(MON_CVAR(mage, attack_spike_smart_trace_min), vlen(self.origin - trace_endpos), self.wait = MON_CVAR(mage, attack_spike_smart_trace_max));
 +
 +                      // Calc how important it is that we turn and add this to the desierd (enemy) dir.
 +                      desireddir  = normalize(((trace_plane_normal * (1 - trace_fraction)) + (desireddir * trace_fraction)) * 0.5);
 +              }
 +              
 +              newdir = normalize(olddir + desireddir * turnrate); // take the average of the 2 directions; not the best method but simple & easy
 +              self.velocity = newdir * spd; // make me fly in the new direction at my flight speed
 +      }
 +      else
 +              dist = 0;
 +              
 +      ///////////////
 +
 +      //self.angles = vectoangles(self.velocity);                     // turn model in the new flight direction
 +      self.nextthink = time;// + 0.05; // csqc projectiles
 +      UpdateCSQCProjectile(self);
 +}
 +
 +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 + MON_CVAR(mage, heal_allies), 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 + MON_CVAR(mage, heal_allies), autocvar_g_balance_armor_regenstable);
 +                                              fx = "armorrepair_fx";
 +                                      }
 +                                      break;
 +                              case 3:
 +                                      head.health = bound(0, head.health - ((head == self)  ? MON_CVAR(mage, heal_self) : MON_CVAR(mage, heal_allies)), 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 + MON_CVAR(mage, heal_allies), head.max_health);
 +                      WaypointSprite_UpdateHealth(head.sprite, head.health);
 +              }
 +      }
 +      
 +      if(washealed)
 +      {
 +              monsters_setframe(mage_anim_attack);
 +              self.attack_finished_single = time + MON_CVAR(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 + MON_CVAR(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 + MON_CVAR(mage, shield_delay);
 +      
 +      monsters_setframe(mage_anim_attack);
 +      self.attack_finished_single = time + 1;
 +      
 +      self.armorvalue = MON_CVAR(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 + MON_CVAR(mage, attack_melee_delay);
 +                      defer(0.2, mageattack_melee);
 +                      
 +                      return TRUE;
 +              }
 +              case MONSTER_ATTACK_RANGED:
 +              {
 +                      if(random() < MON_CVAR(mage, attack_grenade_chance) / 100)
 +                      {
 +                              mage_throw_itemgrenade();
 +                              return TRUE;
 +                      }
 +      
 +                      monsters_setframe(mage_anim_attack);
 +                      self.attack_finished_single = time + MON_CVAR(mage, attack_spike_delay);
 +                      defer(0.2, mage_spike);
 +                      
 +                      return TRUE;
 +              }
 +      }
 +      
 +      return FALSE;
 +}
 +
 +void spawnfunc_monster_mage()
 +{
 +      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 < MON_CVAR(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(MON_CVAR(mage, speed_run), MON_CVAR(mage, speed_walk), MON_CVAR(mage, speed_stop), 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 = MON_CVAR(mage, health);
 +                      
 +                      self.monster_loot = spawnfunc_item_health_large;
 +                      self.monster_attackfunc = mage_attack;
 +                      monsters_setframe(mage_anim_walk);
 +                      
 +                      return TRUE;
 +              }
 +              case MR_INIT:
 +              {
 +                      // nothing
 +                      return TRUE;
 +              }
 +              case MR_CONFIG:
 +              {
 +                      MON_CONFIG_SETTINGS(MAGE_SETTINGS(mage))
 +                      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
index 93897b48048300b141ae0ffa198229c4e1cebd54,0000000000000000000000000000000000000000..31680a278d72d1324fcabdf411b43cf2ad29dd85
mode 100644,000000..100644
--- /dev/null
@@@ -1,170 -1,0 +1,170 @@@
-       sound(self, CH_SHOTS, "weapons/rocket_impact.wav", VOL_BASE, ATTN_NORM);
 +#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")
 +);
 +
 +#define SLIME_SETTINGS(monster) \
 +      MON_ADD_CVAR(monster, health) \
 +      MON_ADD_CVAR(monster, attack_explode_damage) \
 +      MON_ADD_CVAR(monster, speed_stop) \
 +      MON_ADD_CVAR(monster, speed_run) \
 +      MON_ADD_CVAR(monster, speed_walk) 
 +
 +#ifdef SVQC
 +SLIME_SETTINGS(slime)
 +#endif // SVQC
 +#else
 +#ifdef SVQC
 +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, MON_CVAR(slime, attack_explode_damage), 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, MON_CVAR(slime, attack_explode_damage), 15, MON_CVAR(slime, attack_explode_damage) * 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, ATTEN_NORM);
 +      
 +      setmodel(self, "");
 +}
 +
 +void slime_dead()
 +{
 +      self.health = -100; // gibbed
 +      slime_explode();
 +      
 +      self.deadflag = DEAD_DEAD;
 +      self.think = Monster_Fade;
 +      self.nextthink = time + 0.1;
 +}
 +
 +void spawnfunc_monster_slime()
 +{
 +      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(MON_CVAR(slime, speed_run), MON_CVAR(slime, speed_walk), MON_CVAR(slime, speed_stop), 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 = MON_CVAR(slime, health);
 +                      
 +                      self.monster_loot = spawnfunc_item_rockets;
 +                      self.monster_attackfunc = slime_attack;
 +                      monsters_setframe(slime_anim_idle);
 +                      
 +                      return TRUE;
 +              }
 +              case MR_INIT:
 +              {
 +                      // nothing
 +                      return TRUE;
 +              }
 +              case MR_CONFIG:
 +              {
 +                      MON_CONFIG_SETTINGS(SLIME_SETTINGS(slime))
 +                      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
index 7e901af5ac840d3f3046c5aa432f9b78d0883e98,0000000000000000000000000000000000000000..42de2aca49ba89a9341d74bf728c93e8fe8d523d
mode 100644,000000..100644
--- /dev/null
@@@ -1,243 -1,0 +1,243 @@@
-       sound(self, CH_SHOTS, snd, VOL_BASE, ATTN_NORM);
 +#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")
 +);
 +
 +#define SPIDER_SETTINGS(monster) \
 +      MON_ADD_CVAR(monster, health) \
 +      MON_ADD_CVAR(monster, attack_bite_damage) \
 +      MON_ADD_CVAR(monster, attack_bite_delay) \
 +      MON_ADD_CVAR(monster, attack_web_damagetime) \
 +      MON_ADD_CVAR(monster, attack_web_speed) \
 +      MON_ADD_CVAR(monster, attack_web_speed_up) \
 +      MON_ADD_CVAR(monster, attack_web_delay) \
 +      MON_ADD_CVAR(monster, attack_type) \
 +      MON_ADD_CVAR(monster, speed_stop) \
 +      MON_ADD_CVAR(monster, speed_run) \
 +      MON_ADD_CVAR(monster, speed_walk) 
 +
 +#ifdef SVQC
 +SPIDER_SETTINGS(spider)
 +#endif // SVQC
 +#else
 +#ifdef SVQC
 +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 != self) 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, MON_CVAR(spider, attack_web_damagetime), 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';
 +      
 +      monster_makevectors(self.enemy);
 +      
++      sound(self, CH_SHOTS, snd, VOL_BASE, ATTEN_NORM);
 +
 +      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, CENTER_OR_VIEWOFS(self));
 +
 +      //proj.glow_size = 50;
 +      //proj.glow_color = 45;
 +      proj.movetype = MOVETYPE_BOUNCE;
 +      W_SetupProjectileVelocityEx(proj, v_forward, v_up, MON_CVAR(spider, attack_web_speed), MON_CVAR(spider, attack_web_speed_up), 0, 0, FALSE);
 +      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, MON_CVAR(spider, attack_bite_damage), self.attack_range, DEATH_MONSTER_SPIDER, TRUE);
 +                      monsters_setframe((random() > 0.5) ? spider_anim_attack : spider_anim_attack2);
 +                      self.attack_finished_single = time + MON_CVAR(spider, attack_bite_delay);
 +                      
 +                      return TRUE;
 +              }
 +              case MONSTER_ATTACK_RANGED:
 +              {
 +                      if(self.enemy.frozen)
 +                              return FALSE;
 +                      
 +                      monsters_setframe(spider_anim_attack2);
 +                      self.attack_finished_single = time + MON_CVAR(spider, attack_web_delay);
 +                      spider_shootweb(self.spider_type);
 +                      
 +                      return TRUE;
 +              }
 +      }
 +      
 +      return FALSE;
 +}
 +
 +void spawnfunc_monster_spider() 
 +{
 +      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(MON_CVAR(spider, speed_run), MON_CVAR(spider, speed_walk), MON_CVAR(spider, speed_stop), spider_anim_walk, spider_anim_walk, spider_anim_idle);
 +                      return TRUE;
 +              }
 +              case MR_DEATH:
 +              {
 +                      monsters_setframe(spider_anim_attack);
 +                      self.angles += '180 0 0';
 +                      self.SendFlags |= MSF_ANG;
 +                      return TRUE;
 +              }
 +              case MR_SETUP:
 +              {
 +                      if not(self.health) self.health = MON_CVAR(spider, health);
 +                      if not(self.spider_type) self.spider_type = MON_CVAR(spider, attack_type);
 +                      
 +                      self.monster_loot = spawnfunc_item_health_medium;
 +                      self.monster_attackfunc = spider_attack;
 +                      monsters_setframe(spider_anim_idle);
 +                      
 +                      return TRUE;
 +              }
 +              case MR_INIT:
 +              {
 +                      // nothing
 +                      return TRUE;
 +              }
 +              case MR_CONFIG:
 +              {
 +                      MON_CONFIG_SETTINGS(SPIDER_SETTINGS(spider))
 +                      return TRUE;
 +              }
 +      }
 +      
 +      return TRUE;
 +}
 +
 +#endif // SVQC
 +#ifdef CSQC
 +float m_spider(float req)
 +{
 +      switch(req)
 +      {
 +              case MR_DEATH:
 +              {
 +                      // nothing
 +                      return TRUE;
 +              }
 +              case MR_INIT:
 +              {
 +                      precache_model ("models/monsters/spider.dpm");
 +                      return TRUE;
 +              }
 +      }
 +      
 +      return TRUE;
 +}
 +
 +#endif // CSQC
 +#endif // REGISTER_MONSTER
index 53b392ca2cd0fe40ec164069962455f31a7156ea,0000000000000000000000000000000000000000..34586bd245653af73e8f1850e17c295cfa40750c
mode 100644,000000..100644
--- /dev/null
@@@ -1,195 -1,0 +1,195 @@@
-                               self.spawnflags &~= MONSTERFLAG_NORESPAWN; // zombies always respawn
 +#ifdef REGISTER_MONSTER
 +REGISTER_MONSTER(
 +/* MON_##id   */ ZOMBIE,
 +/* function   */ m_zombie,
 +/* spawnflags */ 0,
 +/* mins,maxs  */ '-18 -18 -25', '18 18 47',
 +/* model      */ "zombie.dpm",
 +/* netname    */ "zombie",
 +/* fullname   */ _("Zombie")
 +);
 +
 +#define ZOMBIE_SETTINGS(monster) \
 +      MON_ADD_CVAR(monster, health) \
 +      MON_ADD_CVAR(monster, attack_melee_damage) \
 +      MON_ADD_CVAR(monster, attack_melee_delay) \
 +      MON_ADD_CVAR(monster, attack_leap_damage) \
 +      MON_ADD_CVAR(monster, attack_leap_force) \
 +      MON_ADD_CVAR(monster, attack_leap_speed) \
 +      MON_ADD_CVAR(monster, attack_leap_delay) \
 +      MON_ADD_CVAR(monster, speed_stop) \
 +      MON_ADD_CVAR(monster, speed_run) \
 +      MON_ADD_CVAR(monster, speed_walk) 
 +
 +#ifdef SVQC
 +ZOMBIE_SETTINGS(zombie)
 +#endif // SVQC
 +#else
 +#ifdef SVQC
 +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) * MON_CVAR(zombie, attack_leap_force);
 +              Damage(other, self, self, MON_CVAR(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 + MON_CVAR(zombie, attack_melee_delay);
 +                      
 +                      monster_melee(self.enemy, MON_CVAR(zombie, attack_melee_damage), self.attack_range, 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 * MON_CVAR(zombie, attack_leap_speed) + '0 0 200', MON_CVAR(zombie, attack_leap_delay)))
 +                              return TRUE;
 +              }
 +      }
 +      
 +      return FALSE;
 +}
 +
 +void spawnfunc_monster_zombie() 
 +{
 +      self.classname = "monster_zombie";
 +      
 +      self.monster_spawnfunc = spawnfunc_monster_zombie;
 +      
 +      self.spawnflags |= MONSTER_RESPAWN_DEATHPOINT;
 +      
 +      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(MON_CVAR(zombie, speed_run), MON_CVAR(zombie, speed_walk), MON_CVAR(zombie, speed_stop), 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 = MON_CVAR(zombie, health);
 +                      
 +                      if(self.spawnflags & MONSTERFLAG_NORESPAWN)
++                              self.spawnflags &= ~MONSTERFLAG_NORESPAWN; // zombies always respawn
 +                      
 +                      self.monster_loot = spawnfunc_item_health_medium;
 +                      self.monster_attackfunc = zombie_attack;
 +                      monsters_setframe(zombie_anim_spawn);
 +                      self.spawn_time = time + 2.1;
 +                      self.spawnshieldtime = self.spawn_time;
 +                      self.respawntime = 0.2;
 +                      
 +                      return TRUE;
 +              }
 +              case MR_INIT:
 +              {
 +                      // nothing
 +                      return TRUE;
 +              }
 +              case MR_CONFIG:
 +              {
 +                      MON_CONFIG_SETTINGS(ZOMBIE_SETTINGS(zombie))
 +                      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
index df30e5346876ea75e21f3b3df3c23d583bf09812,0000000000000000000000000000000000000000..4183fe1dd045b14b08f7f6b8d3026f8ec8765c4e
mode 100644,000000..100644
--- /dev/null
@@@ -1,1056 -1,0 +1,1056 @@@
-       sound(self, CHAN_AUTO, msound, VOL_BASE, ATTN_NORM);
 +// =========================
 +//  SVQC Monster Properties
 +// =========================
 +
 +
 +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()
 +{
 +      if(!self.candrop || !self.monster_loot)
 +              return;
 +
 +      vector org = self.origin + ((self.mins + self.maxs) * 0.5);
 +      entity e = spawn();
 +      
 +      setorigin(e, org);
 +      
 +      e.monster_loot = self.monster_loot;
 +      
 +      other = e;
 +      MUTATOR_CALLHOOK(MonsterDropItem);
 +      e = other;
 +      
 +      e.think = monster_item_spawn;
 +      e.nextthink = time + 0.3;
 +}
 +
 +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(targ == ent)
 +              return FALSE; // don't attack ourselves
 +              
 +      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(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(targ.deadflag != DEAD_NO || ent.deadflag != DEAD_NO || targ.health <= 0 || ent.health <= 0)
 +              return FALSE; // enemy/self is dead
 +              
 +      if(ent.monster_owner == targ)
 +              return FALSE; // don't attack our master
 +              
 +      if(targ.monster_owner == ent)
 +              return FALSE; // don't attack our pet
 +      
 +      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 head, closest_target = world;
 +      head = findradius(ent.origin, ent.target_range);
 +                      
 +      while(head) // find the closest acceptable target to pass to
 +      {
 +              if(monster_isvalidtarget(head, ent))
 +              {
 +                      // if it's a player, use the view origin as reference (stolen from RadiusDamage functions in g_damage.qc) 
 +                      vector head_center = CENTER_OR_VIEWOFS(head);
 +                      vector ent_center = CENTER_OR_VIEWOFS(ent);
 +                                      
 +                      //if(ctf_CheckPassDirection(head_center, ent_center, ent.v_angle, head.WarpZone_findradius_nearest))
 +                      if(closest_target)
 +                      {
 +                              vector closest_target_center = CENTER_OR_VIEWOFS(closest_target);
 +                              if(vlen(ent_center - head_center) < vlen(ent_center - closest_target_center))
 +                                      { closest_target = head; }
 +                      }
 +                      else { closest_target = head; }
 +              }
 +              
 +              head = head.chain;
 +      }
 +      
 +      return closest_target;
 +}
 +
 +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
 +
-       self.flags &~= FL_ONGROUND;
++      sound(self, CHAN_AUTO, msound, VOL_BASE, ATTEN_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"));
 +}
 +
 +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);
 +}
 +
 +float monster_melee(entity targ, float damg, float er, float deathtype, float dostop)
 +{
 +      float 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;
 +      }
 +
 +      monster_makevectors(targ);
 +      
 +      traceline(self.origin + self.view_ofs, self.origin + v_forward * er, 0, self);
 +      
 +      if(trace_ent.takedamage)
 +              Damage(targ, self, self, rdmg * monster_skill, deathtype, targ.origin, normalize(targ.origin - self.origin));
 +              
 +      return TRUE;
 +}
 +
 +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;
 +              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;
 +              
 +              self.SendFlags |= MSF_MOVE;
 +              self.SendFlags |= MSF_STATUS;
 +      }
 +      else
 +              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.spawnflags &~= MONSTERFLAG_APPEAR;
++      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(vlen(targ.origin - e.origin) > e.attack_range)
 +      if(e.monster_attackfunc(MONSTER_ATTACK_RANGED))
 +      {
 +              monster_sound(e.msound_attack_ranged, 0, FALSE);
 +              return;
 +      }
 +}
 +
 +void monster_use ()
 +{
 +      if (self.enemy)
 +              return;
 +      if (self.health <= 0)
 +              return;
 +
 +      if(!monster_isvalidtarget(activator, self))
 +              return;
 +
 +      self.enemy = activator;
 +}
 +
 +.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(teamplay)
 +      if(autocvar_g_monsters_teams)
 +      if(IsDifferentTeam(self.monster_owner, self))
 +              self.monster_owner = world;
 +      
 +      if(self.enemy && self.enemy.health < 1)
 +              self.enemy = world; // enough!
 +              
 +      if(time >= self.last_enemycheck)
 +      {
 +              if not(monster_isvalidtarget(self.enemy, self))
 +                      self.enemy = world;
 +                      
 +              if not(self.enemy)
 +              {
 +                      self.enemy = FindTarget(self);
 +                      if(self.enemy)
 +                              monster_sound(self.msound_sight, 0, FALSE);
 +              }
 +                      
 +              self.last_enemycheck = time + 2;
 +      }
 +      
 +      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; 
 +      
 +      if(self.flags & FL_FLY || self.flags & FL_SWIM)
 +              v_forward = normalize(self.moveto - self.origin);
 +
 +      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)
 +              if(vlen(self.velocity) > 0)
 +                      monsters_setframe((self.enemy) ? manim_run : manim_walk);
 +              else
 +                      monsters_setframe(manim_idle);
 +      }
 +      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;
-               spamsound (self, CH_PAIN, "misc/bodyimpact1.wav", VOL_BASE, ATTN_NORM);  // FIXME: PLACEHOLDER
++      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_dropitem();
 +
 +      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;
 +      
 +      // number of monsters spawned with mobspawn command
 +      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, ATTEN_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)
 +      {
 +              // Update one more time to avoid waypoint fading without emptying healthbar
 +              if(self.sprite)
 +                      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);
 +              
 +      if not(self.attack_range)
 +              self.attack_range = autocvar_g_monsters_attack_range;
 +      
 +      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
 +      switch(monster_skill)
 +      {
 +              case 1: if(self.spawnflags & MONSTERSKILL_NOTEASY)              return FALSE; break;
 +              case 2: if(self.spawnflags & MONSTERSKILL_NOTMEDIUM)    return FALSE; break;
 +              case 3: if(self.spawnflags & MONSTERSKILL_NOTHARD)              return FALSE; break;
 +              case 4: if(self.spawnflags & MONSTERSKILL_NOTINSANE)    return FALSE; break;
 +              case 5: if(self.spawnflags & MONSTERSKILL_NOTNIGHTMARE) return FALSE; break;
 +      }
 +
 +      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(mon.spawnflags & MONSTER_SIZE_BROKEN)
 +              self.scale = 1.3;
 +      
 +      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;
 +}
Simple merge
Simple merge
Simple merge
Simple merge
Simple merge
Simple merge
Simple merge
Simple merge
Simple merge
index 62fd76e9a6f9e4bf6f8bb5b5e80d4b21146a1b29,0000000000000000000000000000000000000000..98f5ba4722fd937c42ef8dc948c716586147a42c
mode 100644,000000..100644
--- /dev/null
@@@ -1,355 -1,0 +1,355 @@@
-               sound(self, CH_TRIGGER, "onslaught/shockwave.wav", VOL_BASE, ATTN_NORM);
 +#ifdef CSQC
 +float generator_precached;
 +.float count;
 +
 +vector randompos(vector m1, vector m2)
 +{
 +    vector v;
 +    m2 = m2 - m1;
 +    v_x = m2_x * random() + m1_x;
 +    v_y = m2_y * random() + m1_y;
 +    v_z = m2_z * random() + m1_z;
 +    return  v;
 +}
 +
 +void generator_precache()
 +{
 +      if(generator_precached)
 +              return; // already precached
 +              
 +      precache_model("models/onslaught/generator.md3");
 +      precache_model("models/onslaught/generator_dead.md3");
 +      precache_model("models/onslaught/generator_dmg1.md3");
 +      precache_model("models/onslaught/generator_dmg2.md3");
 +      precache_model("models/onslaught/generator_dmg3.md3");
 +      precache_model("models/onslaught/generator_dmg4.md3");
 +      precache_model("models/onslaught/generator_dmg5.md3");
 +      precache_model("models/onslaught/generator_dmg6.md3");
 +      precache_model("models/onslaught/generator_dmg7.md3");
 +      precache_model("models/onslaught/generator_dmg8.md3");
 +      precache_model("models/onslaught/generator_dmg9.md3");
 +      precache_model("models/onslaught/generator_dead.md3");
 +      
 +      precache_model("models/onslaught/ons_ray.md3");
 +      precache_sound("onslaught/shockwave.wav");
 +      precache_sound("weapons/grenade_impact.wav");
 +      precache_sound("weapons/rocket_impact.wav");
 +      
 +      precache_model("models/onslaught/gen_gib1.md3");
 +      precache_model("models/onslaught/gen_gib2.md3");
 +      precache_model("models/onslaught/gen_gib3.md3");
 +      
 +      generator_precached = TRUE;
 +}
 +
 +void ons_gib_damage (entity inflictor, entity attacker, float damage, float deathtype, vector hitloc, vector vforce)
 +{
 +      self.velocity = self.velocity + vforce;
 +}
 +
 +.float giblifetime;
 +
 +void gib_draw_noburn()
 +{
 +      if(time >= self.giblifetime)
 +              remove(self);
 +}
 +
 +void gib_draw()
 +{
 +      if(time >= self.move_time)
 +              return;
 +
 +      self.move_time = time + 0.05;
 +
 +      if(time > self.giblifetime)
 +      {
 +              remove(self);
 +              return;
 +      }
 +      
 +      self.alpha -= 0.05;
 +      
 +      if(self.alpha < 0.1)
 +      {
 +              remove(self);
 +              return;
 +      }
 +
 +      if(random()<0.6)
 +              pointparticles(particleeffectnum("onslaught_generator_gib_flame"), self.origin, '0 0 0', 1);
 +}
 +
 +void ons_throwgib(vector v_from, vector v_to, string smodel, float f_lifetime, float b_burn)
 +{
 +      entity gib;
 +
 +      gib = spawn();
 +
 +      setmodel(gib, smodel);
 +      setorigin(gib, v_from);
 +      gib.solid = SOLID_CORPSE;
 +      gib.move_movetype = MOVETYPE_BOUNCE;
 +      gib.movetype = MOVETYPE_BOUNCE;
 +      gib.health = 255;
 +      gib.move_velocity = v_to;
 +      gib.move_origin = v_from;
 +      gib.velocity = v_to;
 +      gib.alpha = 1;
 +      gib.move_time = time;
 +      gib.drawmask = MASK_NORMAL;
 +      gib.giblifetime = time + f_lifetime;
 +      
 +      if(b_burn)
 +              gib.draw = gib_draw;
 +      else
 +              gib.draw = gib_draw_noburn;     
 +}
 +
 +void onslaught_generator_ray_think()
 +{
 +      self.nextthink = time + 0.05;
 +      if(self.count > 10)
 +      {
 +              self.think = SUB_Remove;
 +              return;
 +      }
 +
 +      if(self.count > 5)
 +              self.alpha -= 0.1;
 +      else
 +              self.alpha += 0.1;
 +
 +      self.scale += 0.2;
 +      self.count +=1;
 +}
 +
 +void onslaught_generator_ray_spawn(vector org)
 +{
 +      entity e;
 +      e = spawn();
 +      setmodel(e, "models/onslaught/ons_ray.md3");
 +      setorigin(e, org);
 +      e.angles = randomvec() * 360;
 +      e.alpha = 0;
 +      e.scale = random() * 5 + 8;
 +      e.think = onslaught_generator_ray_think;
 +      e.nextthink = time + 0.05;
 +}
 +
 +void generator_draw()
 +{
 +      if(self.health > 0)
 +              return;
 +        
 +      if(time < self.move_time)
 +              return;
 +      if(self.count <= 0)
 +              return;
 +    
 +    vector org;
 +      float i;
 +
 +      // White shockwave
 +      if(self.count==40||self.count==20)
 +      {
-               sound(self, CH_TRIGGER, "weapons/grenade_impact.wav", VOL_BASE, ATTN_NORM);
++              sound(self, CH_TRIGGER, "onslaught/shockwave.wav", VOL_BASE, ATTEN_NORM);
 +              pointparticles(particleeffectnum("electro_combo"), self.origin, '0 0 0', 6);
 +      }
 +
 +      // Throw some gibs
 +      if(random() < 0.3)
 +      {
 +              i = random();
 +              if(i < 0.3)
 +                      ons_throwgib(self.origin + '0 0 40', (100 * randomvec() - '1 1 1') * 11 + '0 0 20', "models/onslaught/gen_gib1.md3", 6, TRUE);
 +              else if(i > 0.7)
 +                      ons_throwgib(self.origin + '0 0 40', (100 * randomvec() - '1 1 1') * 12 + '0 0 20', "models/onslaught/gen_gib2.md3", 6, TRUE);
 +              else
 +                      ons_throwgib(self.origin + '0 0 40', (100 * randomvec() - '1 1 1') * 13 + '0 0 20', "models/onslaught/gen_gib3.md3", 6, TRUE);
 +      }
 +
 +      // Spawn fire balls
 +      for(i=0;i < 10;++i)
 +      {
 +              org = self.origin + randompos('-30 -30 -30' * i + '0 0 -20', '30 30 30' * i + '0 0 20');
 +              pointparticles(particleeffectnum("onslaught_generator_gib_explode"), org, '0 0 0', 1);
 +      }
 +
 +      // Short explosion sound + small explosion
 +      if(random() < 0.25)
 +      {
 +              te_explosion(self.origin);
-               sound(self, CH_TRIGGER, "weapons/rocket_impact.wav", VOL_BASE, ATTN_NORM);
++              sound(self, CH_TRIGGER, "weapons/grenade_impact.wav", VOL_BASE, ATTEN_NORM);
 +      }
 +
 +      // Particles
 +      org = self.origin + randompos(self.mins + '8 8 8', self.maxs + '-8 -8 -8');
 +      pointparticles(particleeffectnum("onslaught_generator_smallexplosion"), org, '0 0 0', 1);
 +
 +      // rays
 +      if(random() > 0.25 )
 +      {
 +              onslaught_generator_ray_spawn(self.origin);
 +      }
 +
 +      // Final explosion
 +      if(self.count==1)
 +      {
 +              org = self.origin;
 +              te_explosion(org);
 +              pointparticles(particleeffectnum("onslaught_generator_finalexplosion"), org, '0 0 0', 1);
++              sound(self, CH_TRIGGER, "weapons/rocket_impact.wav", VOL_BASE, ATTEN_NORM);
 +      }
 +      
 +      self.move_time = time + 0.05;
 +
 +      self.count -= 1;
 +}
 +
 +.float max_health;
 +void generator_damage(float hp)
 +{
 +      if(hp <= 0)
 +              setmodel(self, "models/onslaught/generator_dead.md3");
 +      else if(hp < self.max_health * 0.10)
 +              setmodel(self, "models/onslaught/generator_dmg9.md3");
 +      else if(hp < self.max_health * 0.20)
 +              setmodel(self, "models/onslaught/generator_dmg8.md3");
 +      else if(hp < self.max_health * 0.30)
 +              setmodel(self, "models/onslaught/generator_dmg7.md3");
 +      else if(hp < self.max_health * 0.40)
 +              setmodel(self, "models/onslaught/generator_dmg6.md3");
 +      else if(hp < self.max_health * 0.50)
 +              setmodel(self, "models/onslaught/generator_dmg5.md3");
 +      else if(hp < self.max_health * 0.60)
 +              setmodel(self, "models/onslaught/generator_dmg4.md3");
 +      else if(hp < self.max_health * 0.70)
 +              setmodel(self, "models/onslaught/generator_dmg3.md3");
 +      else if(hp < self.max_health * 0.80)
 +              setmodel(self, "models/onslaught/generator_dmg2.md3");
 +      else if(hp < self.max_health * 0.90)
 +              setmodel(self, "models/onslaught/generator_dmg1.md3");
 +      else if(hp <= self.max_health || hp >= self.max_health)
 +              setmodel(self, "models/onslaught/generator.md3");
 +              
 +      setsize(self, GENERATOR_MIN, GENERATOR_MAX);
 +}
 +
 +void generator_construct()
 +{
 +      self.netname = "Generator";
 +
 +      setorigin(self, self.origin);
 +      setmodel(self, "models/onslaught/generator.md3");
 +      setsize(self, GENERATOR_MIN, GENERATOR_MAX);
 +      
 +      self.move_movetype      = MOVETYPE_NOCLIP;
 +      self.solid                      = SOLID_BBOX;
 +      self.movetype           = MOVETYPE_NOCLIP; 
 +      self.move_origin        = self.origin;
 +      self.move_time          = time;
 +      self.drawmask           = MASK_NORMAL;  
 +      self.alpha                      = 1;
 +      self.draw                       = generator_draw;
 +}
 +
 +.vector glowmod;
 +void generator_changeteam()
 +{
 +      if(self.team)
 +      {
 +              self.glowmod = Team_ColorRGB(self.team - 1);
 +              self.teamradar_color = Team_ColorRGB(self.team - 1);
 +              self.colormap = 1024 + (self.team - 1) * 17;
 +      }
 +      else
 +      {
 +              self.colormap = 1024;
 +              self.glowmod = '1 1 0';
 +              self.teamradar_color = '1 1 0';
 +      }
 +}
 +
 +void ent_generator()
 +{
 +      float sf;
 +      sf = ReadByte();
 +
 +      if(sf & GSF_SETUP)
 +      {
 +              self.origin_x = ReadCoord();
 +              self.origin_y = ReadCoord();
 +              self.origin_z = ReadCoord();
 +              setorigin(self, self.origin);
 +              
 +              self.health = ReadByte();
 +              self.max_health = ReadByte();
 +              self.count = ReadByte();
 +              self.team = ReadByte();
 +              
 +              if not(self.count)
 +                      self.count = 40;
 +              
 +              generator_changeteam();
 +              generator_precache();
 +              generator_construct();
 +      }
 +
 +      if(sf & GSF_STATUS)
 +      {
 +              float _tmp;
 +              _tmp = ReadByte();
 +              if(_tmp != self.team)
 +              {                       
 +                      self.team = _tmp;                               
 +                      generator_changeteam();
 +              }
 +              
 +              _tmp = ReadByte();
 +              
 +              if(_tmp != self.health)
 +                      generator_damage(_tmp);
 +
 +              self.health = _tmp;
 +      }
 +}
 +#endif // CSQC
 +
 +#ifdef SVQC
 +float generator_send(entity to, float sf)
 +{
 +      WriteByte(MSG_ENTITY, ENT_CLIENT_GENERATOR);    
 +      WriteByte(MSG_ENTITY, sf);
 +      if(sf & GSF_SETUP)
 +      {
 +          WriteCoord(MSG_ENTITY, self.origin_x);
 +          WriteCoord(MSG_ENTITY, self.origin_y);
 +          WriteCoord(MSG_ENTITY, self.origin_z);
 +              
 +              WriteByte(MSG_ENTITY, self.health);
 +              WriteByte(MSG_ENTITY, self.max_health);
 +              WriteByte(MSG_ENTITY, self.count);
 +              WriteByte(MSG_ENTITY, self.team);
 +    }
 +    
 +    if(sf & GSF_STATUS)
 +    {
 +              WriteByte(MSG_ENTITY, self.team);
 +      
 +        if(self.health <= 0)
 +            WriteByte(MSG_ENTITY, 0);
 +        else
 +            WriteByte(MSG_ENTITY, ceil((self.health / self.max_health) * 255));
 +    }
 +    
 +      return TRUE;
 +}
 +
 +void generator_link(void() spawnproc)
 +{
 +    Net_LinkEntity(self, TRUE, 0, generator_send);
 +    self.think      = spawnproc;
 +    self.nextthink  = time;
 +}
 +#endif // SVQC
Simple merge
index 988462940c0b9bbda7444f8da2ec5099de42a829,d2fc1e99368ff8c6eeb0669c2844392f1c71f6ff..f09a0c2627a618209cd828d2a04477c06d8d2e74
@@@ -451,11 -451,9 +451,11 @@@ void ctf_Handle_Capture(entity flag, en
  void ctf_Handle_Return(entity flag, entity player)
  {
        // messages and sounds
 -      Send_Notification(NOTIF_ONE, player, MSG_CENTER, APP_TEAM_ENT_2(flag, CENTER_CTF_RETURN_));
 -      Send_Notification(NOTIF_ALL, world, MSG_INFO, APP_TEAM_ENT_2(flag, INFO_CTF_RETURN_), player.netname);
 +      if(IS_PLAYER(player))
 +              Send_Notification(NOTIF_ONE, player, MSG_CENTER, APP_TEAM_ENT_2(flag, CENTER_CTF_RETURN_));
 +              
 +      Send_Notification(NOTIF_ALL, world, MSG_INFO, APP_TEAM_ENT_2(flag, INFO_CTF_RETURN_), (player.flags & FL_MONSTER) ? player.monster_name : player.netname);
-       sound(player, CH_TRIGGER, flag.snd_flag_returned, VOL_BASE, ATTN_NONE);
+       sound(player, CH_TRIGGER, flag.snd_flag_returned, VOL_BASE, ATTEN_NONE);
        ctf_EventLog("return", flag.team, player);
  
        // scoring
Simple merge
Simple merge
Simple merge
index 5dfaf0f0955cf292a7ec23ca4b37d812d0c73bcf,75fa9407e6858b05393a4bdb5ce749f2bc99ec12..7226fe5679be74bd90b1122c8ad6dc79859f7bb4
@@@ -817,10 -810,9 +817,10 @@@ void vehicles_exit(float eject
  
      _vehicle.team = _vehicle.tur_head.team;
          
-     sound (_vehicle, CH_TRIGGER_SINGLE, "misc/null.wav", 1, ATTN_NORM);
+     sound (_vehicle, CH_TRIGGER_SINGLE, "misc/null.wav", 1, ATTEN_NORM);
      _vehicle.vehicle_hudmodel.viewmodelforclient = _vehicle;  
      _vehicle.phase = time + 1;
 +      _vehicle.monster_attack = FALSE;
      
      _vehicle.vehicle_exit(eject);
      
Simple merge