From 84e75ec07a5027e76726bc28073438d8c9a8d04a Mon Sep 17 00:00:00 2001
From: Tim Angus <tim@ngus.net>
Date: Fri, 12 Sep 2003 04:36:26 +0000
Subject: * Removed the func_door_model auto scaling and positioning code - it
 didn't work * Added the server side stuff for the hive

---
 src/cgame/cg_animmapobj.c |  53 ++---------
 src/cgame/cg_ents.c       |  30 ++++++-
 src/cgame/cg_event.c      |   3 +
 src/cgame/cg_weapons.c    |  12 ++-
 src/game/bg_misc.c        |  55 ++++++++++++
 src/game/bg_public.h      |  31 ++++---
 src/game/g_buildable.c    |  83 ++++++++++++++++-
 src/game/g_local.h        |   1 +
 src/game/g_missile.c      | 225 +++++++++++++++++++++++++++++++++++++++-------
 src/game/g_mover.c        |  28 +-----
 src/game/g_weapon.c       |  20 +++++
 src/game/tremulous.h      |  20 ++++-
 12 files changed, 432 insertions(+), 129 deletions(-)

(limited to 'src')

diff --git a/src/cgame/cg_animmapobj.c b/src/cgame/cg_animmapobj.c
index 46b64073..2fc98bc6 100644
--- a/src/cgame/cg_animmapobj.c
+++ b/src/cgame/cg_animmapobj.c
@@ -125,11 +125,12 @@ void CG_ModelDoor( centity_t *cent )
 {
   refEntity_t     ent;
   entityState_t   *es;
-  vec3_t          mins, maxs, size;
+  vec3_t          mins, maxs, size, rotSize;
   vec3_t          cMins, cMaxs, displacement;
   vec3_t          bMaxs, scale;
   animation_t     anim;
   lerpFrame_t     *lf = &cent->lerpFrame;
+  float           temp;
     
 
   es = &cent->currentState;
@@ -149,51 +150,11 @@ void CG_ModelDoor( centity_t *cent )
   ent.skinNum = 0;
   ent.hModel = cgs.gameModels[ es->modelindex ];
 
-  if( es->eFlags & EF_NO_AUTO_SCALE )
-  {
-    //this door is being manually scaled
-    VectorScale( ent.axis[ 0 ], es->origin2[ 0 ], ent.axis[ 0 ] );
-    VectorScale( ent.axis[ 1 ], es->origin2[ 1 ], ent.axis[ 1 ] );
-    VectorScale( ent.axis[ 2 ], es->origin2[ 2 ], ent.axis[ 2 ] );
-    ent.nonNormalizedAxes = qtrue;
-  }
-  else
-  {
-    //automatically scale and position the model
-    trap_R_ModelBounds( ent.hModel, mins, maxs );
-
-    //average of mins and maxs
-    VectorSubtract( maxs, mins, size );
-    VectorScale( size, 0.5f, size );
-    
-    //set corrected bbox
-    VectorCopy( size, cMaxs );
-    VectorNegate( size, cMins );
-
-    //calculate the displacement needed to align
-    //the model with the centre of the brush
-    VectorSubtract( cMins, mins, displacement );
-
-    VectorCopy( es->angles2, bMaxs );
-
-    //scale the axis and displacement by the ratio
-    //of the brush to corrected bboxes
-    scale[ 0 ] = bMaxs[ 0 ] / cMaxs[ 0 ];
-    scale[ 1 ] = bMaxs[ 1 ] / cMaxs[ 1 ];
-    scale[ 2 ] = bMaxs[ 2 ] / cMaxs[ 2 ];
-    
-    VectorScale( ent.axis[ 0 ], scale[ 0 ], ent.axis[ 0 ] );
-    VectorScale( ent.axis[ 1 ], scale[ 1 ], ent.axis[ 1 ] );
-    VectorScale( ent.axis[ 2 ], scale[ 2 ], ent.axis[ 2 ] );
-    ent.nonNormalizedAxes = qtrue;
-
-    displacement[ 0 ] = scale[ 0 ] * displacement[ 0 ];
-    displacement[ 1 ] = scale[ 1 ] * displacement[ 1 ];
-    displacement[ 2 ] = scale[ 2 ] * displacement[ 2 ];
-
-    VectorAdd( ent.origin, displacement, ent.origin );
-    VectorAdd( ent.oldorigin, displacement, ent.oldorigin );
-  }
+  //scale the door
+  VectorScale( ent.axis[ 0 ], es->origin2[ 0 ], ent.axis[ 0 ] );
+  VectorScale( ent.axis[ 1 ], es->origin2[ 1 ], ent.axis[ 1 ] );
+  VectorScale( ent.axis[ 2 ], es->origin2[ 2 ], ent.axis[ 2 ] );
+  ent.nonNormalizedAxes = qtrue;
 
   //setup animation
   anim.firstFrame   = es->powerups;
diff --git a/src/cgame/cg_ents.c b/src/cgame/cg_ents.c
index 6a1fa650..c7590ca0 100644
--- a/src/cgame/cg_ents.c
+++ b/src/cgame/cg_ents.c
@@ -323,6 +323,7 @@ static void CG_Missile( centity_t *cent )
   vec3_t              up;
   float               fraction;
   int                 index;
+  qboolean            switchBugWorkaround = qfalse;
 
   s1 = &cent->currentState;
   if( s1->weapon > WP_NUM_WEAPONS )
@@ -367,7 +368,7 @@ static void CG_Missile( centity_t *cent )
       ent.rotation = 0;
       ent.customShader = cgs.media.blasterShader;
       trap_R_AddRefEntityToScene( &ent );
-      return;
+      switchBugWorkaround = qtrue;
       break;
 
     case WP_PULSE_RIFLE:
@@ -376,7 +377,7 @@ static void CG_Missile( centity_t *cent )
       ent.rotation = 0;
       ent.customShader = cgs.media.plasmaBallShader;
       trap_R_AddRefEntityToScene( &ent );
-      return;
+      switchBugWorkaround = qtrue;
       break;
 
     case WP_LUCIFER_CANNON:
@@ -398,9 +399,27 @@ static void CG_Missile( centity_t *cent )
       
       break;
 
+    case WP_HIVE:
+      //FIXME:
+      ent.reType = RT_SPRITE;
+      ent.radius = 4;
+      ent.rotation = 0;
+      ent.customShader = cgs.media.blasterShader;
+      trap_R_AddRefEntityToScene( &ent );
+      switchBugWorkaround = qtrue;
+      break;
+      
+/*    case WP_LOCKBLOB_LAUNCHER:
+      //FIXME:
+      break;*/
+      
+/*    case WP_POUNCE_UPG:
+      //FIXME:
+      break;*/
+      
     case WP_FLAMER:
       //TA: don't actually display the missile (use the particle engine)
-      return;
+      switchBugWorkaround = qtrue;
       break;
 
     default:
@@ -410,7 +429,7 @@ static void CG_Missile( centity_t *cent )
       ent.renderfx = weapon->missileRenderfx | RF_NOSHADOW;
 
       // convert direction of travel into axis
-      if ( VectorNormalize2( s1->pos.trDelta, ent.axis[ 0 ] ) == 0 )
+      if( VectorNormalize2( s1->pos.trDelta, ent.axis[ 0 ] ) == 0 )
         ent.axis[ 0 ][ 2 ] = 1;
 
       // spin as it moves
@@ -420,6 +439,9 @@ static void CG_Missile( centity_t *cent )
         RotateAroundDirection( ent.axis, s1->time );
   }
   
+  if( switchBugWorkaround )
+    return;
+  
   // add to refresh list, possibly with quad glow
   CG_AddRefEntityWithPowerups( &ent, s1->powerups, TEAM_FREE );
 }
diff --git a/src/cgame/cg_event.c b/src/cgame/cg_event.c
index 7d8f38c5..5907228a 100644
--- a/src/cgame/cg_event.c
+++ b/src/cgame/cg_event.c
@@ -111,6 +111,9 @@ static void CG_Obituary( entityState_t *ent )
     case MOD_SLOWBLOB:
       message = "should have visited a medical station";
       break;
+    case MOD_SWARM:
+      message = "was hunted down by the swarm";
+      break;
     default:
       message = NULL;
       break;
diff --git a/src/cgame/cg_weapons.c b/src/cgame/cg_weapons.c
index 343e359d..7fd3b627 100644
--- a/src/cgame/cg_weapons.c
+++ b/src/cgame/cg_weapons.c
@@ -1559,6 +1559,7 @@ void CG_MissileHitWall( int weapon, int clientNum, vec3_t origin, vec3_t dir, im
   int           duration;
   vec3_t        sprOrg;
   vec3_t        sprVel;
+  qboolean      switchBugWorkaround = qfalse;
 
   mark = 0;
   radius = 32;
@@ -1576,7 +1577,6 @@ void CG_MissileHitWall( int weapon, int clientNum, vec3_t origin, vec3_t dir, im
 
   switch( weapon )
   {
-    default:
     case WP_TESLAGEN:
     case WP_AREA_ZAP:
     case WP_DIRECT_ZAP:
@@ -1672,8 +1672,18 @@ void CG_MissileHitWall( int weapon, int clientNum, vec3_t origin, vec3_t dir, im
                          spark, qfalse, qfalse );
       }
       break;
+
+    case WP_HIVE:
+      switchBugWorkaround = qtrue;
+      break;
+
+    default:
+      break;
   }
 
+  if( switchBugWorkaround )
+    return;
+
   if( sfx )
     trap_S_StartSound( origin, ENTITYNUM_WORLD, CHAN_AUTO, sfx );
 
diff --git a/src/game/bg_misc.c b/src/game/bg_misc.c
index 01033261..eca4e53d 100644
--- a/src/game/bg_misc.c
+++ b/src/game/bg_misc.c
@@ -152,6 +152,39 @@ buildableAttributes_t bg_buildableList[ ] =
     qfalse,                //qboolean  dccTest;
     qfalse                 //qboolean  reactorTest;
   },
+  {
+    BA_A_HIVE,             //int       buildNum;
+    "hive",                //char      *buildName;
+    "Hive",                //char      *humanName;
+    "team_alien_hive",     //char      *entityName;
+    { "models/buildables/acid_tube/acid_tube.md3", 0, 0, 0 },
+    { -35, -35, -11 },     //vec3_t    mins;
+    { 35, 35, 40 },        //vec3_t    maxs;
+    TR_GRAVITY,            //trType_t  traj;
+    0.0,                   //float     bounce;
+    HIVE_BP,               //int       buildPoints;
+    ( 1 << S1 )|( 1 << S2 )|( 1 << S3 ), //int  stages
+    HIVE_HEALTH,           //int       health;
+    HIVE_REGEN,            //int       regenRate;
+    HIVE_SPLASHDAMAGE,     //int       splashDamage;
+    HIVE_SPLASHRADIUS,     //int       splashRadius;
+    MOD_ASPAWN,            //int       meansOfDeath;
+    BIT_ALIENS,            //int       team;
+    ( 1 << WP_ABUILD )|( 1 << WP_ABUILD2 ),    //weapon_t  buildWeapon;
+    BANIM_IDLE1,           //int       idleAnim;
+    500,                   //int       nextthink;
+    10000,                 //int       buildTime;
+    qfalse,                //qboolean  usable;
+    0,                     //int       turretRange;
+    0,                     //int       turretFireSpeed;
+    WP_HIVE,               //weapon_t  turretProjType;
+    0.707f,                //float     minNormal;
+    qtrue,                 //qboolean  invertNormal;
+    qtrue,                 //qboolean  creepTest;
+    HIVE_CREEPSIZE,        //int       creepSize;
+    qfalse,                //qboolean  dccTest;
+    qfalse                 //qboolean  reactorTest;
+  },
   {
     BA_A_TRAPPER,          //int       buildNum;
     "trapper",             //char      *buildName;
@@ -2621,6 +2654,28 @@ weaponAttributes_t bg_weapons[ ] =
     0,                    //int       buildDelay;
     WUT_ALIENS            //WUTeam_t  team;
   },
+  {
+    WP_HIVE,              //int       weaponNum;
+    0,                    //int       price;
+    ( 1 << S1 )|( 1 << S2 )|( 1 << S3 ), //int  stages
+    SLOT_WEAPON,          //int       slots;
+    "hive",               //char      *weaponName;
+    "Hive",               //char      *weaponHumanName;
+    0,                    //int       quan;
+    0,                    //int       clips;
+    0,                    //int       maxClips;
+    qtrue,                //int       infiniteAmmo;
+    qfalse,               //int       usesEnergy;
+    500,                  //int       repeatRate1;
+    500,                  //int       repeatRate2;
+    500,                  //int       repeatRate3;
+    0,                    //int       reloadTime;
+    qfalse,               //qboolean  hasAltMode;
+    qfalse,               //qboolean  hasThirdMode;
+    qfalse,               //qboolean  purchasable;
+    0,                    //int       buildDelay;
+    WUT_ALIENS            //WUTeam_t  team;
+  },
   {
     WP_MGTURRET,          //int       weaponNum;
     0,                    //int       price;
diff --git a/src/game/bg_public.h b/src/game/bg_public.h
index a519595f..494c3cf7 100644
--- a/src/game/bg_public.h
+++ b/src/game/bg_public.h
@@ -268,20 +268,20 @@ typedef enum
 #define EF_PLAYER_EVENT     0x00000004
 #define EF_BOUNCE           0x00000008    // for missiles
 #define EF_BOUNCE_HALF      0x00000010    // for missiles
-#define EF_WALLCLIMB        0x00000020    // TA: wall walking
-#define EF_WALLCLIMBCEILING 0x00000040    // TA: wall walking ceiling hack
-#define EF_NODRAW           0x00000080    // may have an event, but no model (unspawned items)
-#define EF_FIRING           0x00000100    // for lightning gun
-#define EF_FIRING2          0x00000200    // alt fire
-#define EF_FIRING3          0x00000400    // third fire
-#define EF_MOVER_STOP       0x00000800    // will push otherwise
-#define EF_TALK             0x00001000    // draw a talk balloon
-#define EF_CONNECTION       0x00002000    // draw a connection trouble sprite
-#define EF_VOTED            0x00004000    // already cast a vote
-#define EF_TEAMVOTED        0x00008000    // already cast a vote
-#define EF_OVERDRAW_OFF     0x00010000    // TA: disable overdraw protection on sprites
-#define EF_REAL_LIGHT       0x00020000    // TA: light sprites according to ambient light
-#define EF_NO_AUTO_SCALE    0x00040000    // TA: don't auto scale model doors
+#define EF_NO_BOUNCE_SOUND  0x00000020    // for missiles
+#define EF_WALLCLIMB        0x00000040    // TA: wall walking
+#define EF_WALLCLIMBCEILING 0x00000080    // TA: wall walking ceiling hack
+#define EF_NODRAW           0x00000100    // may have an event, but no model (unspawned items)
+#define EF_FIRING           0x00000200    // for lightning gun
+#define EF_FIRING2          0x00000400    // alt fire
+#define EF_FIRING3          0x00000800    // third fire
+#define EF_MOVER_STOP       0x00001000    // will push otherwise
+#define EF_TALK             0x00002000    // draw a talk balloon
+#define EF_CONNECTION       0x00004000    // draw a connection trouble sprite
+#define EF_VOTED            0x00008000    // already cast a vote
+#define EF_TEAMVOTED        0x00010000    // already cast a vote
+#define EF_OVERDRAW_OFF     0x00020000    // TA: disable overdraw protection on sprites
+#define EF_REAL_LIGHT       0x00040000    // TA: light sprites according to ambient light
 
 typedef enum
 {
@@ -335,6 +335,7 @@ typedef enum
   WP_PAIN_SAW,
 
   WP_LOCKBLOB_LAUNCHER,
+  WP_HIVE,
   WP_TESLAGEN,
   WP_MGTURRET,
   
@@ -397,6 +398,7 @@ typedef enum
   BA_A_ACIDTUBE,
   BA_A_TRAPPER,
   BA_A_BOOSTER,
+  BA_A_HIVE,
   
   BA_A_HOVEL,
   
@@ -825,6 +827,7 @@ typedef enum
 
   MOD_SLOWBLOB,
   MOD_POISON,
+  MOD_SWARM,
   
   MOD_HSPAWN,
   MOD_TESLAGEN,
diff --git a/src/game/g_buildable.c b/src/game/g_buildable.c
index 6ff424f0..50b64f1c 100644
--- a/src/game/g_buildable.c
+++ b/src/game/g_buildable.c
@@ -663,7 +663,7 @@ void ABarricade_Die( gentity_t *self, gentity_t *inflictor, gentity_t *attacker,
 ================
 ABarricade_Think
 
-think function for Alien Barricade
+Think function for Alien Barricade
 ================
 */
 void ABarricade_Think( gentity_t *self )
@@ -694,7 +694,7 @@ void AAcidTube_Think( gentity_t *self );
 ================
 AAcidTube_Damage
 
-damage function for Alien Acid Tube
+Damage function for Alien Acid Tube
 ================
 */
 void AAcidTube_Damage( gentity_t *self )
@@ -729,7 +729,7 @@ void AAcidTube_Damage( gentity_t *self )
 ================
 AAcidTube_Think
 
-think function for Alien Acid Tube
+Think function for Alien Acid Tube
 ================
 */
 void AAcidTube_Think( gentity_t *self )
@@ -782,6 +782,77 @@ void AAcidTube_Think( gentity_t *self )
 
 
 
+/*
+================
+AHive_Think
+
+Think function for Alien Hive
+================
+*/
+void AHive_Think( gentity_t *self )
+{
+  int       entityList[ MAX_GENTITIES ];
+  vec3_t    range = { ACIDTUBE_RANGE, ACIDTUBE_RANGE, ACIDTUBE_RANGE };
+  vec3_t    mins, maxs;
+  int       i, num;
+  gentity_t *enemy;
+  vec3_t    dirToTarget;
+
+  self->nextthink = level.time + BG_FindNextThinkForBuildable( self->s.modelindex );
+  
+  VectorAdd( self->s.origin, range, maxs );
+  VectorSubtract( self->s.origin, range, mins );
+  
+  //if there is no creep nearby die
+  if( !findCreep( self ) )
+  {
+    G_Damage( self, NULL, NULL, NULL, NULL, 10000, 0, MOD_SUICIDE );
+    return;
+  }
+
+  if( self->timestamp < level.time )
+    self->active = qfalse; //nothing has returned in HIVE_REPEAT seconds, forget about it
+  
+  if( self->spawned && !self->active )
+  {
+    //do some damage
+    num = trap_EntitiesInBox( mins, maxs, entityList, MAX_GENTITIES );
+    for( i = 0; i < num; i++ )
+    {
+      enemy = &g_entities[ entityList[ i ] ];
+      
+      if( enemy->health <= 0 )
+        continue;
+
+      if( enemy->client && enemy->client->ps.stats[ STAT_PTEAM ] == PTE_HUMANS )
+      {
+        self->active = qtrue;
+        self->target_ent = enemy;
+        self->timestamp = level.time + HIVE_REPEAT;
+        
+        VectorSubtract( enemy->s.pos.trBase, self->s.pos.trBase, dirToTarget );
+        VectorNormalize( dirToTarget );
+        vectoangles( dirToTarget, self->turretAim );
+          
+        //fire at target
+        FireWeapon( self );
+        G_setBuildableAnim( self, BANIM_ATTACK1, qfalse );
+        return;
+      }
+    }
+  }
+
+  creepSlow( self->s.modelindex, self->s.origin );
+}
+
+
+
+
+//==================================================================================
+
+
+
+
 #define HOVEL_TRACE_DEPTH 16.0f
 
 /*
@@ -2091,6 +2162,12 @@ gentity_t *G_buildItem( gentity_t *builder, buildable_t buildable, vec3_t origin
       built->pain = ASpawn_Pain;
       break;
       
+    case BA_A_HIVE:
+      built->die = ABarricade_Die;
+      built->think = AHive_Think;
+      built->pain = ASpawn_Pain;
+      break;
+      
     case BA_A_TRAPPER:
       built->die = ABarricade_Die;
       built->think = ATrapper_Think;
diff --git a/src/game/g_local.h b/src/game/g_local.h
index e65d1e69..9d78b932 100644
--- a/src/game/g_local.h
+++ b/src/game/g_local.h
@@ -656,6 +656,7 @@ gentity_t *fire_luciferCannon( gentity_t *self, vec3_t start, vec3_t dir, int da
 gentity_t *fire_lockblob( gentity_t *self, vec3_t start, vec3_t dir );
 gentity_t *fire_paraLockBlob( gentity_t *self, vec3_t start, vec3_t dir );
 gentity_t *fire_slowBlob( gentity_t *self, vec3_t start, vec3_t dir );
+gentity_t *fire_hive( gentity_t *self, vec3_t start, vec3_t dir );
 
 
 //
diff --git a/src/game/g_missile.c b/src/game/g_missile.c
index 76f3cd10..8eb4169f 100644
--- a/src/game/g_missile.c
+++ b/src/game/g_missile.c
@@ -89,6 +89,7 @@ void G_ExplodeMissile( gentity_t *ent )
   trap_LinkEntity( ent );
 }
 
+void AHive_ReturnToHive( gentity_t *self );
 
 /*
 ================
@@ -98,38 +99,25 @@ G_MissileImpact
 */
 void G_MissileImpact( gentity_t *ent, trace_t *trace )
 {
-  gentity_t   *other;
-  qboolean    hitClient = qfalse;
+  gentity_t   *other, *attacker;
+  qboolean    returnAfterDamage = qfalse;
 
   other = &g_entities[ trace->entityNum ];
+  attacker = &g_entities[ ent->r.ownerNum ];
 
   // check for bounce
   if( !other->takedamage &&
       ( ent->s.eFlags & ( EF_BOUNCE | EF_BOUNCE_HALF ) ) )
   {
     G_BounceMissile( ent, trace );
-    G_AddEvent( ent, EV_GRENADE_BOUNCE, 0 );
-    return;
-  }
+    
+    //only play a sound if requested
+    if( !( ent->s.eFlags & EF_NO_BOUNCE_SOUND ) )
+      G_AddEvent( ent, EV_GRENADE_BOUNCE, 0 );
 
-  // impact damage
-  if( other->takedamage )
-  {
-    // FIXME: wrong damage direction?
-    if( ent->damage )
-    {
-      vec3_t  velocity;
-
-      BG_EvaluateTrajectoryDelta( &ent->s.pos, level.time, velocity );
-      if( VectorLength( velocity ) == 0 )
-        velocity[ 2 ] = 1;  // stepped on a grenade
-      
-      G_Damage( other, ent, &g_entities[ ent->r.ownerNum ], velocity,
-        ent->s.origin, ent->damage, 
-        0, ent->methodOfDeath );
-    }
+    return;
   }
-
+  
   if( !strcmp( ent->classname, "lockblob" ) )
   {
     if( other->client && other->client->ps.stats[ STAT_PTEAM ] == PTE_HUMANS )
@@ -148,7 +136,54 @@ void G_MissileImpact( gentity_t *ent, trace_t *trace )
       VectorCopy( other->client->ps.viewangles, other->client->ps.grapplePoint );
     }
   }
+  else if( !strcmp( ent->classname, "hive" ) )
+  {
+    if( other->s.eType == ET_BUILDABLE && other->s.modelindex == BA_A_HIVE )
+    {
+      if( !ent->parent )
+        G_Printf( S_COLOR_YELLOW "WARNING: hive entity has no parent in G_MissileImpact\n" );
+      else
+        ent->parent->active = qfalse;
+      
+      G_FreeEntity( ent );
+      return;
+    }
+    else
+    {
+      //prevent collision with the client when returning
+      ent->r.ownerNum = other->s.number;
+
+      ent->think = AHive_ReturnToHive;
+      ent->nextthink = level.time + FRAMETIME;
+      
+      //only damage humans
+      if( other->client && other->client->ps.stats[ STAT_PTEAM ] == PTE_HUMANS )
+        returnAfterDamage = qtrue;
+      else
+        return;
+    }
+  }
   
+  // impact damage
+  if( other->takedamage )
+  {
+    // FIXME: wrong damage direction?
+    if( ent->damage )
+    {
+      vec3_t  velocity;
+
+      BG_EvaluateTrajectoryDelta( &ent->s.pos, level.time, velocity );
+      if( VectorLength( velocity ) == 0 )
+        velocity[ 2 ] = 1;  // stepped on a grenade
+      
+      G_Damage( other, ent, attacker, velocity, ent->s.origin, ent->damage, 
+        0, ent->methodOfDeath );
+    }
+  }
+
+  if( returnAfterDamage )
+    return;
+
   // is it cheaper in bandwidth to just remove this ent and create a new
   // one, rather than changing the missile into the explosion?
 
@@ -195,16 +230,9 @@ void G_RunMissile( gentity_t *ent )
   // get current position
   BG_EvaluateTrajectory( &ent->s.pos, level.time, origin );
 
-  // if this missile bounced off an invulnerability sphere
-  if ( ent->target_ent )
-  {
-    passent = ent->target_ent->s.number;
-  }
-  else
-  {
-    // ignore interactions with the missile owner
-    passent = ent->r.ownerNum;
-  }
+  // ignore interactions with the missile owner
+  passent = ent->r.ownerNum;
+  
   // trace a line from the previous position to the current position
   trap_Trace( &tr, ent->r.currentOrigin, ent->r.mins, ent->r.maxs, origin, passent, ent->clipmask );
 
@@ -233,7 +261,7 @@ void G_RunMissile( gentity_t *ent )
     }
 
     G_MissileImpact( ent, &tr );
-    if ( ent->s.eType != ET_MISSILE )
+    if( ent->s.eType != ET_MISSILE )
       return;   // exploded
   }
 
@@ -417,6 +445,136 @@ gentity_t *fire_luciferCannon( gentity_t *self, vec3_t start, vec3_t dir, int da
 
 //=============================================================================
 
+/*
+================
+AHive_ReturnToHive
+
+Adjust the trajectory to point towards the hive
+================
+*/
+void AHive_ReturnToHive( gentity_t *self )
+{
+  vec3_t  dir;
+  trace_t tr;
+
+  if( !self->parent )
+  {
+    G_Printf( S_COLOR_YELLOW "WARNING: AHive_ReturnToHive called with no self->parent\n" );
+    return;
+  }
+  
+  trap_UnlinkEntity( self->parent );
+  trap_Trace( &tr, self->r.currentOrigin, self->r.mins, self->r.maxs,
+              self->parent->r.currentOrigin, self->r.ownerNum, self->clipmask );
+  trap_LinkEntity( self->parent );
+
+  if( tr.fraction < 1.0f )
+  {
+    //if can't see hive then disperse
+    VectorCopy( self->r.currentOrigin, self->s.pos.trBase );
+    self->s.pos.trType = TR_STATIONARY;
+    self->s.pos.trTime = level.time;
+
+    self->think = G_ExplodeMissile;
+    self->nextthink = level.time + 2000;
+    self->parent->active = qfalse; //allow the parent to start again
+  }
+  else
+  {
+    VectorSubtract( self->parent->r.currentOrigin, self->r.currentOrigin, dir );
+    VectorNormalize( dir );
+    
+    //change direction towards the hive
+    VectorScale( dir, HIVE_SPEED, self->s.pos.trDelta );
+    SnapVector( self->s.pos.trDelta );      // save net bandwidth
+    VectorCopy( self->r.currentOrigin, self->s.pos.trBase );
+    self->s.pos.trTime = level.time;
+
+    self->think = G_ExplodeMissile;
+    self->nextthink = level.time + 15000;
+  }
+}
+
+/*
+================
+AHive_SearchAndDestroy
+
+Adjust the trajectory to point towards the target
+================
+*/
+void AHive_SearchAndDestroy( gentity_t *self )
+{
+  vec3_t dir;
+  trace_t tr;
+  
+  trap_Trace( &tr, self->r.currentOrigin, self->r.mins, self->r.maxs,
+              self->target_ent->r.currentOrigin, self->r.ownerNum, self->clipmask );
+
+  //if there is no LOS or the parent hive is too far away or the target is dead, return
+  if( tr.entityNum == ENTITYNUM_WORLD ||
+      Distance( self->r.currentOrigin, self->parent->r.currentOrigin ) > ( HIVE_RANGE * 5 ) ||
+      self->target_ent->health <= 0 )
+  {
+    self->r.ownerNum = ENTITYNUM_WORLD;
+
+    self->think = AHive_ReturnToHive;
+    self->nextthink = level.time + FRAMETIME;
+  }
+  else
+  {
+    VectorSubtract( self->target_ent->r.currentOrigin, self->r.currentOrigin, dir );
+    VectorNormalize( dir );
+    
+    //change direction towards the player
+    VectorScale( dir, HIVE_SPEED, self->s.pos.trDelta );
+    SnapVector( self->s.pos.trDelta );      // save net bandwidth
+    VectorCopy( self->r.currentOrigin, self->s.pos.trBase );
+    self->s.pos.trTime = level.time;
+
+    self->nextthink = level.time + HIVE_DIR_CHANGE_PERIOD;
+  }
+}
+
+/*
+=================
+fire_hive
+=================
+*/
+gentity_t *fire_hive( gentity_t *self, vec3_t start, vec3_t dir )
+{
+  gentity_t *bolt;
+
+  VectorNormalize ( dir );
+
+  bolt = G_Spawn( );
+  bolt->classname = "hive";
+  bolt->nextthink = level.time + HIVE_DIR_CHANGE_PERIOD;
+  bolt->think = AHive_SearchAndDestroy;
+  bolt->s.eType = ET_MISSILE;
+  bolt->s.eFlags |= EF_BOUNCE|EF_NO_BOUNCE_SOUND;
+  bolt->r.svFlags = SVF_USE_CURRENT_ORIGIN;
+  bolt->s.weapon = WP_HIVE;
+  bolt->r.ownerNum = self->s.number;
+  bolt->parent = self;
+  bolt->damage = HIVE_DMG;
+  bolt->splashDamage = 0;
+  bolt->splashRadius = 0;
+  bolt->methodOfDeath = MOD_SWARM;
+  bolt->clipmask = MASK_SHOT;
+  bolt->target_ent = self->target_ent;
+
+  bolt->s.pos.trType = TR_LINEAR;
+  bolt->s.pos.trTime = level.time - MISSILE_PRESTEP_TIME;   // move a bit on the very first frame
+  VectorCopy( start, bolt->s.pos.trBase );
+  VectorScale( dir, HIVE_SPEED, bolt->s.pos.trDelta );
+  SnapVector( bolt->s.pos.trDelta );      // save net bandwidth
+  VectorCopy( start, bolt->r.currentOrigin );
+
+  return bolt;
+}
+
+//=============================================================================
+
 /*
 =================
 fire_lockblob
@@ -440,6 +598,7 @@ gentity_t *fire_lockblob( gentity_t *self, vec3_t start, vec3_t dir )
   bolt->damage = 0;
   bolt->splashDamage = 0;
   bolt->splashRadius = 0;
+  bolt->methodOfDeath = MOD_UNKNOWN; //doesn't do damage so will never kill
   bolt->clipmask = MASK_SHOT;
   bolt->target_ent = NULL;
 
diff --git a/src/game/g_mover.c b/src/game/g_mover.c
index ea7f40be..3d171343 100644
--- a/src/game/g_mover.c
+++ b/src/game/g_mover.c
@@ -1601,8 +1601,6 @@ void SP_func_door_model( gentity_t *ent )
   char      *sound;
   gentity_t *door;
   gentity_t *clipBrush;
-  vec3_t    scale;
-  qboolean  dontAutoScale = qfalse;
 
   G_SpawnString( "sound2to1", "sound/movers/doors/dr1_strt.wav", &s );
   ent->sound2to1 = G_SoundIndex( s );
@@ -1638,27 +1636,9 @@ void SP_func_door_model( gentity_t *ent )
   VectorCopy( clipBrush->r.mins, ent->r.mins );
   VectorCopy( clipBrush->r.maxs, ent->r.maxs );
   
-  if( !VectorLength( ent->s.origin ) )
-  {
-    //calculate the centre of the brush
-    VectorSubtract( ent->r.maxs, ent->r.mins, size );
-    VectorScale( size, 0.5f, size );
-    VectorAdd( size, ent->r.mins, ent->s.origin );
-  }
-  else
-    dontAutoScale = qtrue;
-
-  if( G_SpawnVector( "scale", "1 1 1", scale ) )
-    dontAutoScale = qtrue;
-  
-  //let the client know the scaling of the model
-  VectorCopy( scale, ent->s.origin2 );
-  
-  //let the client know the bounds of the brush
-  VectorCopy( size, ent->s.angles2 );
+  G_SpawnVector( "modelOrigin", "0 0 0", ent->s.origin );
 
-  if( dontAutoScale )
-    ent->s.eFlags |= EF_NO_AUTO_SCALE;
+  G_SpawnVector( "scale", "1 1 1", ent->s.origin2 );
   
   // if the "model2" key is set, use a seperate model
   // for drawing, but clip against the brushes
@@ -1703,10 +1683,12 @@ void SP_func_door_model( gentity_t *ent )
   ent->moverState = MODEL_POS1;
   ent->s.eType = ET_MODELDOOR;
   VectorCopy( ent->s.origin, ent->s.pos.trBase );
+  ent->s.pos.trType = TR_STATIONARY;
   ent->s.pos.trTime = 0;
   ent->s.pos.trDuration = 0;
   VectorClear( ent->s.pos.trDelta );
   VectorCopy( ent->s.angles, ent->s.apos.trBase );
+  ent->s.apos.trType = TR_STATIONARY;
   ent->s.apos.trTime = 0;
   ent->s.apos.trDuration = 0;
   VectorClear( ent->s.apos.trDelta );
@@ -1722,8 +1704,6 @@ void SP_func_door_model( gentity_t *ent )
   
   trap_LinkEntity( ent );
 
-  ent->s.pos.trType = TR_STATIONARY;
-
   if( !( ent->flags & FL_TEAMSLAVE ) )
   {
     int health;
diff --git a/src/game/g_weapon.c b/src/game/g_weapon.c
index 2590503e..fd44840c 100644
--- a/src/game/g_weapon.c
+++ b/src/game/g_weapon.c
@@ -212,6 +212,23 @@ void lockBlobLauncherFire( gentity_t *ent )
 /*
 ======================================================================
 
+HIVE
+
+======================================================================
+*/
+
+void hiveFire( gentity_t *ent )
+{
+  gentity_t *m;
+
+  m = fire_hive( ent, muzzle, forward );
+
+//  VectorAdd( m->s.pos.trDelta, ent->client->ps.velocity, m->s.pos.trDelta );  // "real" physics
+}
+
+/*
+======================================================================
+
 BLASTER PISTOL
 
 ======================================================================
@@ -1101,6 +1118,9 @@ void FireWeapon( gentity_t *ent )
     case WP_LOCKBLOB_LAUNCHER:
       lockBlobLauncherFire( ent );
       break;
+    case WP_HIVE:
+      hiveFire( ent );
+      break;
     case WP_TESLAGEN:
       teslaFire( ent );
       break;
diff --git a/src/game/tremulous.h b/src/game/tremulous.h
index b50be9d9..3ee4bbd7 100644
--- a/src/game/tremulous.h
+++ b/src/game/tremulous.h
@@ -215,6 +215,18 @@
 #define ACIDTUBE_RANGE              200.0f
 #define ACIDTUBE_REPEAT             10000
 
+#define HIVE_BP                     50
+#define HIVE_HEALTH                 ABHM(100)
+#define HIVE_REGEN                  10
+#define HIVE_SPLASHDAMAGE           30
+#define HIVE_SPLASHRADIUS           200
+#define HIVE_CREEPSIZE              120
+#define HIVE_RANGE                  400.0f
+#define HIVE_REPEAT                 10000
+#define HIVE_DMG                    40
+#define HIVE_SPEED                  230.0f
+#define HIVE_DIR_CHANGE_PERIOD      500
+
 #define TRAPPER_BP                  150
 #define TRAPPER_HEALTH              ABHM(80)
 #define TRAPPER_REGEN               8
@@ -467,10 +479,10 @@
 #define HUMAN_BACK_MODIFIER         0.7f
 #define HUMAN_SIDE_MODIFIER         0.8f
 
-#define STAMINA_STOP_RESTORE        10
-#define STAMINA_WALK_RESTORE        5
-#define STAMINA_SPRINT_TAKE         10
-#define STAMINA_LARMOUR_TAKE        5
+#define STAMINA_STOP_RESTORE        20
+#define STAMINA_WALK_RESTORE        10
+#define STAMINA_SPRINT_TAKE         6
+#define STAMINA_LARMOUR_TAKE        3
 
 /*
  * Misc
-- 
cgit