                          FLOATING QUAKE ENTITIES

Would you like to see gibs, heads, dead bodies and backpacks float in
water? Now you can by following this tutorial and with a little extra
programming you could make other entities float (how about a grenade that
starts to surface after it reaches the floor of the water).

---------------------------------------------------------------------------

PART 1 - PREPARING A NEW PATCH DIRECTORY

To start, you should have an unmodified copy of the QuakeC v1.06 source
code and a copy of the file FLOATER.QC that accompanies this tutorial.

Create a directory called FLOATER in the Quake directory as you would
normally do for any patch and then create another directory inside it
called SRC.

Now copy the QuakeC source code, as well as the new source file FLOATER.QC,
into the SRC directory.

---------------------------------------------------------------------------

PART 2 - ADDING A NEW SOURCE FILE

As we are introducing a new file, we must add it to the patch, the file
PROGS.SRC informs the compiler which source files to include in a patch.

Add FLOATER.QC to PROGS.SRC (4) as follows:
(The 4 in brackets above is the line number where you will be starting the
changes and will be shown for each modification throughout the tutorial)

  defs.qc
  floater.qc // floating entity routines
  subs.qc
  fight.qc

(I have included some existing lines from above and below where the
modification will be made, this shows how the changed file will look)

---------------------------------------------------------------------------

PART 3 - MODIFYING EXISTING SOURCE CODE

Modifications start and finish with a comment line of "// FLOATER". Please
include all comments when you are making the modification as otherwise the
line numbers given for each change will be wrong.

Modify CLIENT.QC (904) as follows:

  local float mspeed, aspeed;
  local float r;

  // FLOATER
  // controls the floating of the floaters
  floaterPreThink();
  // FLOATER

  if (intermission_running)

By adding a call to floaterPreThink, we are giving Quake the ability to
control floating entities (floaters). This routine checks (every frame) if
any floaters exist, they are processed depending on their current state
which can be, falling, surfacing, floating or sinking.

---------------------------------------------------------------------------

PART 4 - GIVING AN ENTITY THE ABILITY TO FLOAT

A floater will sink depending on its falling velocity, it will then float
to the surface where it will bob up and down for around 30 seconds and
finally it will sink until it hits the ground.

Sometimes you'll find a floater will retain its avelocity, making it spin
around while bobbing - I didn't code for anything like this. Also an entity
will sometimes keep its velocity_x or velocity_y.

A maximum of 32 (configurable - see FLOATER.QC) floaters can be active at
one time - should this be the case when another is enabled then the oldest
will be disabled (it will drop to the ground using normal quake physics
movement).

To enable an entity's floating ability we must call a new routine found in
FLOATER.QC called floaterEnable.

Modify PLAYER.QC (466) as follows:

  new.origin = self.origin;
  setmodel (new, gibname);

  // FLOATER
  // give it a Z axis size so it'll show up in water
  // as well as out of water
  setsize( new, '0 0 -4', '0 0 12' );
  // FLOATER

  new.velocity = VelocityForDamage (dm);
  new.movetype = MOVETYPE_BOUNCE;

The reason for changing the size of the gib is so that it'll show up in the
water as well as out of the water when its bobbing on the surface.

(Next PLAYER.QC modification starts at line 484)

  new.frame = 0;
  new.flags = 0;

  // FLOATER
  // make the gib float
  floaterEnable( new, 2 );
  // FLOATER

There are other modifications still to be made, but if you want to compile
the patch and run quake with it to see gibs floating in water, you can do
so at this point.

The call to floaterEnable sets up the gib so that it will float should it
enter water.

Two parameters are passed to floaterEnable.

The first parameter is the entity being enabled.

The second parameter is an offset added to the entity's origin used when
working out if the entity is in water or out of water.

You can go straight to PART 5, if you arent interested in making entities
(with the exception of those mentioned above) float in water.

Calculate the origin offset as follows:

Firstly, this is how I see an entity's origin in quake:
(I have limited knowledge so this is probably not how I should be thinking
about it but until I find the time to read up on it, it'll have to do ;)

Quake encases a bounding box around an entity in the format 'X Y Z' (below
origin, called mins), 'X Y Z' (above origin, called maxs) to give it a
particular size (maxs - mins) and an origin (size - maxs).

The following diagram shows a head (heh use your imagination ;) which has a
bounding box of '-16 -16 0', '16 16 56':

  ======   <--- top of bounding box (56)

  ------
  [head]
  ------   <--- bottom of bounding box (0), origin (0)
  ~~~~~~   <--- surface of water

(diagram not actual size ;)

When we check if the head is in water or not, we use its origin. In the
case of our head above, it would hardly ever be in the water as it's origin
is too close to the bottom of the bounding box.

Only the origin's Z axis is important when making the head float properly.
We use (56 - 0) - 56 to calculate it, the result is 0 and is relative to
the bottom of the bounding box.

To get the head bobbing properly we would pass as the second parameter to
floaterEnable, a value of 5 (I use 5 because the appearance (model) of the
head is in most cases only about 20% of its bounding box size) this would
make the head's water checking origin as follows:

  ======   <--- top of bounding box (56)

  ------
  [head]   <--- water checking origin (5)
  ------   <--- bottom of bounding box (0), origin (0)
  ~~~~~~   <--- surface of water

Here is another example, this time its a dead body, its bounding box
extents are: '-16 -16 -24', '16 16 32'

  ======   <--- top of bounding box (56)

           <--- origin (24)
  ------
  [body]   <--- water checking origin (6)
  ------

  ======   <--- bottom of bounding box
  ~~~~~~   <--- surface of the water

In the case of the dead body, we need an offset of -18 (6 - 24) to get it
to float in water properly.

I realise the above explanation isn't all that easy to get to grips with (I
found it quite difficult to explain) but if you try different offset values
depending on the bounding box of the entity I'm sure you'll be able to see
how it works.

---------------------------------------------------------------------------

PART 5 - COMPLETING THE PATCH

Modify CLIENT.QC (483) as follows:

  void() PutClientInServer =
  {
     local entity spot;

     // FLOATER
     // only dead players float
     floaterDisable( self );
     // FLOATER

     spot = SelectSpawnPoint ();

(Next CLIENT.QC modification starts at line 791)

  if (self.movetype == MOVETYPE_NOCLIP)
     return;

  // FLOATER
  // this was a check for < 0 but a player is also dead
  // when his health == 0, the reason for this minor change
  // is that if the player dies with a health of zero, then this
  // routine actually goes through the motions which causes
  // the player not to float properly!
  if( self.health <= 0 )
     return;
  // FLOATER

  if (self.waterlevel != 3)

Modify PLAYER.QC (504) as follows:

  self.flags = self.flags - (self.flags & FL_ONGROUND);
  self.avelocity = crandom() * '0 600 0';

  // FLOATER
  // make the head float
  floaterEnable( self, 5 );
  // FLOATER

(Next PLAYER.QC modification starts at line 573)

  self.angles_x = 0;
  self.angles_z = 0;

  // FLOATER
  // make the player's dead body float
  floaterEnable( self, -18 );
  // FLOATER

  if (self.weapon == IT_AXE)

Modify COMBAT.QC (78) as follows:

  if (self.flags & FL_MONSTER)
  {
     // FLOATER
     // make the monster's dead body float
     floaterEnable( self, -18 );
     // FLOATER

     killed_monsters = killed_monsters + 1;
     WriteByte (MSG_ALL, SVC_KILLEDMONSTER);
  }

Modify WORLD.QC (390) as follows:

  setorigin (bodyque_head, ent.origin);
  setsize (bodyque_head, ent.mins, ent.maxs);

  // FLOATER
  bodyque_head.sFloating = ent.sFloating;
  bodyque_head.state = ent.state;
  bodyque_head.speed = ent.speed;

  bodyque_head.fOriginOffset = ent.fOriginOffset;

  bodyque_head.ltime = ent.ltime;

  if( ent.flags & FL_INWATER )
     bodyque_head.flags = bodyque_head.flags | FL_INWATER;
  // FLOATER

  bodyque_head = bodyque_head.owner;

The above modification makes the copy of a dead player have the same state
as the player before he respawns.

Modify ITEMS.QC (1380) as follows:

  item.nextthink = time + 120;  // remove after 2 minutes
  item.think = SUB_Remove;

  // FLOATER
  // make the backpack float
  floaterEnable( item, 6 );
  // FLOATER

---------------------------------------------------------------------------

Thats all there is to it, compile the patch then run quake with it.

Now that you've got it working with an unmodified version, you can easily
do it to another patch out there. How about playing Dissolution of Eternity
(this pack is great, it has so much atmosphere! - the QuakeC source code is
available on Rogue's web page) with floating entities.

I gave the example of grenades floating in water, I think the easiest way
is to make it a floater as soon as it is created. That way if it does enter
water, it'll act accordingly, probably if it does hit water you'll want to
add time to it's nextthink so it explodes a little later.

Have fun!

One final thing, I'd like to say a big thank you to id Software for QuakeC.

---------------------------------------------------------------------------

/*
==============================================================================
FLOATER.QC by Alan Kivlin <e-mail: alan.kivin@cyberiacafe.co.uk>

12 / MAY / 1997

Description of the Modification
-------------------------------

Routines for making an entity float to the surface of water.

A floater will sink depending on its falling velocity, it will then float
to the surface where it will bob up and down for around 30 seconds and
finally it will sink until it hits the ground.

Sometimes you'll find a floater will retain its avelocity, making it
spin around while bobbing - I didn't code for anything like this. Also an
entity will sometimes keep it's velocity_x or velocity_y.

Copyright and Distribution Permissions
--------------------------------------

This modification is Copyright (C) 1997 by Alan Kivlin.

Authors MAY use this modification as a basis for other
publicly available work.
^^^^^^^^

Use this in a commercial endeavour and become a friend of Satan. Talk to me
first, ok?

DISCLAIMER: Alan Kivlin (aka Virtuoso) is not responsible for any harm or
psychological affects, loss of sleep, fatigue or general irresponsibility
from using this modification.

If you put this on a CD, you owe me one free copy of the CD. You also owe
everyone in the world a Quake related competition, with the prize being a
free copy of the CD to the first 10 competition winners. You pay postage
too. Don't like this? Don't put it on a CD!

If you take my work and rip my name off, you will burn in hell.

Thanks to Dave 'Zoid' Kirsch for his Copyright and Distribution Permissions
that I based the above on.
==============================================================================
*/

// last frame processed
float fFloaterLastFrame;

// "floating" when the entity is a floater
.string sFloating;

// origin offset when checking for water
.float fOriginOffset;

// floater state flags
float FS_FALLING   = 1;
float FS_SURFACING = 2;
float FS_FLOATING  = 3;
float FS_SINKING   = 4;

// maximum number of active floaters
float FLOATER_MAXIMUM = 32;

//----------------------------------------------------------------------------

// returns TRUE if in WATER, SLIME or LAVA
float( entity ent ) floaterInWater;

// makes the entity float
void( entity ent, float offset ) floaterEnable;

// stops the entity from floating
void( entity ent ) floaterDisable;

// controls the floating of the floaters
void() floaterPreThink;

//----------------------------------------------------------------------------

/*
==============================================================================
floaterInWater

returns TRUE if in WATER, SLIME or LAVA
==============================================================================
*/

float( entity ent ) floaterInWater =
{
   local vector where;
   local float contents;

   where = ent.origin;
   where_z = where_z + ent.fOriginOffset;

   contents = pointcontents( where );

   if( contents >= -5 && contents <= -3 )
      // is in WATER (-3), SLIME (-4) or LAVA (-5)
      return TRUE;

   return FALSE;
};

/*
==============================================================================
floaterEnable

makes the entity float
==============================================================================
*/

void( entity ent, float offset ) floaterEnable =
{
   local float floatercount;
   local entity floater, oldest;

   oldest = floater = find( world, sFloating, "floating" );

   while( floater )
   {
      floatercount = floatercount + 1;

      if( floater.ltime <= oldest.ltime )
         oldest = floater;

      floater = find( floater, sFloating, "floating" );
   }

   if( floatercount == FLOATER_MAXIMUM )
      floaterDisable( oldest );

   ent.sFloating = "floating";
   ent.state = FS_FALLING;
   ent.speed = 0;

   // save origin offset, used when checking for water
   ent.fOriginOffset = offset;

   // time to start sinking
   ent.ltime = time + 30 + random() * 5;

   if( floaterInWater( ent ) )
   {
      // MOVETYPE_TOSS in water
      ent.movetype = MOVETYPE_TOSS;

      // set inwater flag
      ent.flags = ent.flags | FL_INWATER;
   }
   else
   {
      // MOVETYPE_BOUNCE out of water
      ent.movetype = MOVETYPE_BOUNCE;

      // reset inwater flag
      ent.flags = ent.flags - ( ent.flags & FL_INWATER );
   }
};

/*
==============================================================================
floaterDisable

stops the entity from floating
==============================================================================
*/

void( entity ent ) floaterDisable =
{
   // stop floating
   ent.sFloating = string_null;
};

/*
==============================================================================
floaterPreThink

controls the floating of the floaters
==============================================================================
*/

void() floaterPreThink =
{
   local entity ent;

   if( fFloaterLastFrame == framecount )
      // already processed this frame
      return;

   // set last frame so we don't process a frame more than once
   fFloaterLastFrame = framecount;

   ent = find( world, sFloating, "floating" );

   while( ent )
   {
      if( ( ent.state == FS_FLOATING ) && ( ent.flags & FL_ONGROUND ) )
      {
         // if we are on the ground then we should be falling, this occurs
         // when the floater is on a moving platform that has left the water
         ent.state = FS_FALLING;
         ent.speed = 0;
      }

      if( ent.state == FS_FLOATING )
      {
         if( ent.speed > 0 )
            // floating up
            ent.velocity_z = ent.speed * ( 1 + frametime * 8 );
         else
            // floating down
            ent.velocity_z = 0;
      }
      else if( ent.state == FS_SURFACING )
      {
         if( ent.velocity_z > 0 )
            // keep surfacing to a constant speed
            ent.velocity_z = ent.speed;
         else if( floaterInWater( ent ) )
            // can't reach the surface so make it sink
            ent.state = FS_SINKING;
      }
      else if( ent.state == FS_SINKING )
         if( ent.flags & FL_ONGROUND )
            // sunk to the bottom
            floaterDisable( ent );
         else
            // sink slowly
            ent.velocity_z = 0;

      if( floaterInWater( ent ) )
      {
         if( ! ( ent.flags & FL_INWATER ) )
         {
            // MOVETYPE_TOSS in water
            ent.movetype = MOVETYPE_TOSS;

            // set inwater flag
            ent.flags = ent.flags | FL_INWATER;

            if( ent.state == FS_FLOATING )
               // start floating up
               ent.speed = 72 + random() * 16;
            else if( ent.state == FS_FALLING )
               // play enter water sound
               sound( ent, CHAN_BODY, "player/h2ojump.wav", 1, ATTN_NORM );
         }

         if( ent.state == FS_FALLING )
         {
            if( ! ent.speed )
            {
               if( ent.velocity_z < 0 )
                  // set maximum falling speed before floater should surface
                  ent.speed = ent.velocity_z * 8;
            }

            if( ( ent.velocity_z <= ent.speed ) || ( ent.flags & FL_ONGROUND ) )
            {
               // start surfacing
               ent.state = FS_SURFACING;
               ent.speed = 128 + random() * 32;

               ent.velocity_z = ent.speed;
               ent.velocity_x = 0;
               ent.velocity_y = 0;
            }
         }
      }
      else
      {
         if( ( ent.flags & FL_INWATER ) )
         {
            // MOVETYPE_BOUNCE out of water
            ent.movetype = MOVETYPE_BOUNCE;

            // reset inwater flag
            ent.flags = ent.flags - FL_INWATER;

            if( ent.state == FS_FLOATING )
               // start floating down
               ent.speed = 0;
            else if( ent.state == FS_SINKING )
            {
               // floater has sunk out of the water
               ent.state = FS_FALLING;
               ent.speed = 0;
            }

            if( ent.state == FS_FALLING )
               // play leave water sound
               sound( ent, CHAN_BODY, "player/h2ojump.wav", 1, ATTN_NORM );
         }

         if( ent.state == FS_SURFACING )
         {
            // once its surfaced, make it jump out
            ent.velocity_z = ent.speed * 1.5;

            // start floating down
            ent.state = FS_FLOATING;
            ent.speed = 0;
         }
      }

      if( ent.ltime <= time )
      {
         if( ent.flags & FL_INWATER )
         {
            // floater has taken in too much water so sink to the bottom
            ent.state = FS_SINKING;
            ent.ltime = time + 10 + random() * 5;
         }
      }

      // stop quake from making a splash sound
      ent.watertype = 0;

      // physics movement won't happen otherwise
      ent.flags = ent.flags - ( ent.flags & FL_ONGROUND );

      ent = find( ent, sFloating, "floating" );
   }
};

---------------------------------------------------------------------------
 e-mail: alan.kivlin@cyberiacafe.co.uk   Copyright (C) 1997 by Alan Kivlin
