Slime - The lost material type

henke37

aa
Sep 23, 2011
2,075
515
I am sure that most of us have at least noticed the existence of a dev/dev_slime material when skimming the dev folder in the texture browser. But what does it do?

Let's start by looking at the material itself:

Code:
// dev/dev_slime.vmt
"LightmappedGeneric"
{
    // Original shader: BaseTimesLightmap
    "%compileSlime" 1
    "$basetexture" "Dev/dev_slime"
    "$surfaceprop" "slime"
}

Ok, so it passes the %compileSlime flag to vbsp. What does that mean?

Code:
// from int FindMiptex (const char *name) in textures.cpp
// https://github.com/Noiwex/hl2sb-src/blob/master/src/utils/vbsp/textures.cpp#L261
    if ( ( propVal = GetMaterialVar( matID, "%compileSlime" ) ) &&
       StringIsTrue( propVal ) )
     {
       textureref[i].contents &= ~(CONTENTS_SOLID|CONTENTS_DETAIL);
       textureref[i].contents |= CONTENTS_SLIME;
       textureref[i].flags |= SURF_NODECALS;
       // Set this so that we can check at the end of the process the presence of a a WaterLODControl entity.
       g_bHasWater = true;
     }

Now it's passed along as CONTENTS_SLIME. Lets see where the game code checks for that!

First up we have some old quake code. I can only assume that Valve didn't keep this one when they redesigned their physics system.
Code:
//void JumpButton (void)
//http://osxr.org:8080/android/source/external/quake/quake/src/QW/client/pmove.c#0667
  if (waterlevel >= 2)
  {  // swimming, not jumping
  onground = -1;
  if (watertype == CONTENTS_WATER)
  pmove.velocity[2] = 100;
  else if (watertype == CONTENTS_SLIME)
  pmove.velocity[2] = 80;
  else
  pmove.velocity[2] = 50;
  return;
  }

Next up we have some mentions in the HL2 source code:
Code:
// https://github.com/ValveSoftware/source-sdk-2013/blob/master/sp/src/game/server/basebludgeonweapon.cpp#L236
bool CBaseHLBludgeonWeapon::ImpactWater( const Vector &start, const Vector &end )
{
   //FIXME: This doesn't handle the case of trying to splash while being underwater, but that's not going to look good
   //      right now anyway...
   
   // We must start outside the water
   if ( UTIL_PointContents( start ) & (CONTENTS_WATER|CONTENTS_SLIME))
     return false;

   // We must end inside of water
   if ( !(UTIL_PointContents( end ) & (CONTENTS_WATER|CONTENTS_SLIME)))
     return false;

   trace_t   waterTrace;

   UTIL_TraceLine( start, end, (CONTENTS_WATER|CONTENTS_SLIME), GetOwner(), COLLISION_GROUP_NONE, &waterTrace );

   if ( waterTrace.fraction < 1.0f )
   {
     CEffectData   data;

     data.m_fFlags  = 0;
     data.m_vOrigin = waterTrace.endpos;
     data.m_vNormal = waterTrace.plane.normal;
     data.m_flScale = 8.0f;

     // See if we hit slime
     if ( waterTrace.contents & CONTENTS_SLIME )
     {
       data.m_fFlags |= FX_WATER_IN_SLIME;
     }

     DispatchEffect( "watersplash", data );       
   }

   return true;
}

There is also a mention in physics_fx.cpp, again for making splashes.

Both of these use the FX_WATER_IN_SLIME constant. Lets see where that is used:
Code:
// http://hlssmod.net/he_code/game/client/fx_water.cpp
void SplashCallback( const CEffectData &data )
{
    Vector    normal;

    AngleVectors( data.m_vAngles, &normal );

    if ( data.m_fFlags & FX_WATER_IN_SLIME )
    {
        FX_GunshotSlimeSplash( data.m_vOrigin, Vector(0,0,1), data.m_flScale );
    }
    else
    {
        FX_GunshotSplash( data.m_vOrigin, Vector(0,0,1), data.m_flScale );
    }
}

DECLARE_CLIENT_EFFECT( "watersplash", SplashCallback );


void GunshotSplashCallback( const CEffectData &data )
{
    if ( data.m_fFlags & FX_WATER_IN_SLIME )
    {
        FX_GunshotSlimeSplash( data.m_vOrigin, Vector(0,0,1), data.m_flScale );
    }
    else
    {
        FX_GunshotSplash( data.m_vOrigin, Vector(0,0,1), data.m_flScale );
    }
}

DECLARE_CLIENT_EFFECT( "gunshotsplash", GunshotSplashCallback );

Looks like we are getting close to a meaningful difference in behaviour!

Code:
void FX_GunshotSlimeSplash( const Vector &origin, const Vector &normal, float scale )
{
   if ( cl_show_splashes.GetBool() == false )
     return;

   VPROF_BUDGET( "FX_GunshotSlimeSplash", VPROF_BUDGETGROUP_PARTICLE_RENDERING );
   
#if 0
// cut for brevity since it's not used
// it's more or less identical to the code of FX_GunshotSplash, but with a different effect material
#else
   
   QAngle vecAngles;
   VectorAngles( normal, vecAngles );
   if ( scale < 2.0f )
   {
     DispatchParticleEffect( "slime_splash_01", origin, vecAngles );
   }
   else if ( scale < 4.0f )
   {
     DispatchParticleEffect( "slime_splash_02", origin, vecAngles );
   }
   else
   {
     DispatchParticleEffect( "slime_splash_03", origin, vecAngles );
   }

#endif

   //Play a sound
   CLocalPlayerFilter filter;

   EmitSound_t ep;
   ep.m_nChannel = CHAN_VOICE;
   ep.m_pSoundName =  "Physics.WaterSplash";
   ep.m_flVolume = 1.0f;
   ep.m_SoundLevel = SNDLVL_NORM;
   ep.m_pOrigin = &origin;

   C_BaseEntity::EmitSound( filter, SOUND_FROM_WORLD, ep );
}

So, this ultimately ends up as water that has a different particle effect when shot at. Clearly an important difference from water.

If anyone know of any more differences, please just post them in this topic.
 

Pocket

Half a Lambert is better than one.
aa
Nov 14, 2009
4,694
2,579
I don't know about any of this code, but I'm pretty sure the slime surfaceprop is for slippery surfaces. Like in Half-Life 2 when you encounter the buttload of barnacles you're supposed to blow up with the explosive barrels, and near the end of Portal when you're in the trenches in the huge room where all the turrets pop out. I never did find out if it's meant to be something you stand in or something you stand on since you can't see your own feet in either of those games, but if it's based on water, I guess it's the former.