Skybox changing and water on the ground

A lot of people have been asking me how to do things like change the skybox on the fly, make the ground look like it’s been covered in water, etc.

Since I believe that withholding code is a stupid and childish thing to do, I’m going to show you all how to do it!

This is not by any means a release and is more of a thing for developers. Sticking the code into files and running it will error. I’ve left out bits and pieces out, but they arn’t too hard to figure out. The basic techniques are all that are needed.

If you use any code it would be great if you could credit the right people.

I’ll start with the easy stuff, water on the ground:

This is clientside.

If the weather is a ‘wet’ one, we run the code. We make sure the person is under the skybox, and therefore is in a wet area. If they are, we check which foot has just hit the ground. We then spawn the particle effects that that position!

I use the ‘legs’ addon so that you actually see that you have legs… It looks a bit wierd having splashes under your invisible torso.

function GM:PlayerFootstep( ply, pos, foot, sound, volume, rf )
if !ply:IsOnGround() then return end
if CurrentWeather == WEATHER_STORMY or CurrentWeather == WEATHER_STORMY_HEAVY or CurrentWeather == WEATHER_RAINY or CurrentWeather == WEATHER_JUICY then
local Filter = ply;
if ply:InVehicle() then
Filter = {ply, ply:GetVehicle()}

	local TrUp = {}
	TrUp.start = ply:GetShootPos();
	TrUp.endpos = ply:GetShootPos() + Vector(0, 0, 10000);
	TrUp.filter = Filter;

	local TrRes = util.TraceLine(TrUp);

	TrRes.Hit = TrRes.Hit and !TrRes.HitSky;
	if !TrRes.Hit then
		local BonePos = pos
		local BoneAng = nil
		if foot == 0 then -- left
			local BoneIndx = ply:LookupBone("ValveBiped.Bip01_L_Foot")
			BonePos , BoneAng = ply:GetBonePosition( BoneIndx )
		else -- right
			local BoneIndx = ply:LookupBone("ValveBiped.Bip01_R_Foot")
			BonePos , BoneAng = ply:GetBonePosition( BoneIndx )
		local effectdata = EffectData() 
		util.Effect( "watersplash", effectdata )
		local TrUp2 = {}
		TrUp2.start = ply:GetShootPos();
		TrUp2.endpos = ply:GetShootPos() - Vector(0, 0, 1000);
		TrUp2.filter = Filter;

		local TrRes2 = util.TraceLine(TrUp2);
		if foot == 0 then
			ParticleEffect("water_foam_01",TrRes2.HitPos,Angle(0,0,0),nil) -- Required EP2!


EP2 particle effects need to be spawned on the server before they can be run on the client, I don’t know why… That may not even be it, but if I don’t run the following code on the server on start the particle effects are just red crosses.

local ParticleSystem = ents.Create( “info_particle_system” )
ParticleSystem:SetKeyValue( “effect_name”, “water_foam_01”)
ParticleSystem:SetKeyValue( “start_active”, 1)
ParticleSystem:Fire(“kill”, “”, 5)


The skybox stuff:

Serverside, we need to find out which skybox is being used in the first place.
hook.Add(“EntityKeyValue”, “JSSkybox:KeyValue”,
function (ent,key,val)
if (ent:GetClass() == “worldspawn” && key == “skyname”) then
SetGlobalString(“JSSkybox:SkyName”, val)

Then clientside we can mess around with it!

In a think hook on the client:

if (GetGlobalString(“JSSkybox:SkyName”) == “”) then return end

local skyname = GetGlobalString("JSSkybox:SkyName")
if !SetSky and skyname then
	JSSkybox.Materials = {}
	JSSkybox.Materials[1] = Material("skybox/" .. skyname .. "up")
	JSSkybox.Materials[2] = Material("skybox/" .. skyname .. "dn")
	JSSkybox.Materials[3] = Material("skybox/" .. skyname .. "lf")
	JSSkybox.Materials[4] = Material("skybox/" .. skyname .. "rt")
	JSSkybox.Materials[5] = Material("skybox/" .. skyname .. "bk")
	JSSkybox.Materials[6] = Material("skybox/" .. skyname .. "ft")
	print("------SkyBox Stuff------")
	for k,v in pairs(JSSkybox.Materials) do
	SetSky = true

if LastCurrentSkyboxR != CurrentSkyboxR or LastCurrentSkyboxG != CurrentSkyboxG or LastCurrentSkyboxB != CurrentSkyboxB then
	for k,v in pairs(JSSkybox.Materials) do
		v:SetMaterialVector("$color", Vector(CurrentSkyboxR, CurrentSkyboxG, CurrentSkyboxB))
	LastCurrentSkyboxR = CurrentSkyboxR
	LastCurrentSkyboxG = CurrentSkyboxG
	LastCurrentSkyboxB = CurrentSkyboxB


This handles skybox colour changes and the grabbing of the textures.

This actually changes the skybox:

local skybox_suffix = {}
skybox_suffix[1] = “up”
skybox_suffix[2] = “dn”
skybox_suffix[3] = “lf”
skybox_suffix[4] = “rt”
skybox_suffix[5] = “bk”
skybox_suffix[6] = “ft”
if WeatherEffects[CurrentWeather].skybox then
for k,v in pairs(JSSkybox.Materials) do
v:SetMaterialTexture("$basetexture",Material(“skybox/” … WeatherEffects[CurrentWeather].skybox … skybox_suffix[k]):GetMaterialTexture("$basetexture"))
v:SetMaterialVector("$color", Vector(1,1,1))
for k,v in pairs(JSSkybox.Materials) do
v:SetMaterialTexture("$basetexture",Material(“skybox/” … GetGlobalString(“JSSkybox:SkyName”) … skybox_suffix[k]):GetMaterialTexture("$basetexture"))
v:SetMaterialVector("$color", Vector(1,1,1))

I used to use SetMaterialString with $basetexture but it failed horribly… The above method works well, and resets on map change! (As far as I know…)

There are minor problems with this method, for example, the textures don’t fade, they just jump instantly to the new ones. Also, having a night skybox and the lightmaps being bright just looks wierd, I used some clever colourmod trickery to deal with that.

Thanks for the feedback, if anyone could work out ways to improve on the idea it would be appreciated if you could share them, just as I have done.


Really nice, thanks.

That’s pretty badass. Thanks for this!

If you stick this in DarkRP my RP server won’t have much special in it :S

Except for not being poorly coded. Good job and thank you.


This should be run on both the server and the client though.

Just as _KillBurn said. You have to precache them in shared before usage. It’s in the wiki! :wink:

I didn’t read the wiki :wink:

I actually meant to bother you about this, given that I thought this was going to be done with magic and the clientside skybox hooks.

Thanks for sharing.

Possibly helpful:

Create six render targets ( I know that sounds like a lot, but you need one for each side ).

Clientside, replace each of the sides $basetexture with the matching render target.

Alpha blend between the two desired skyboxes ( might not work if skybox textures don’t have $vertexalpha set, or may require custom vmts for the skyboxes ) inside their relevant rendertargets.

It’s how the insurgency scopes on Wizey!'s SWEPs work as far as I can guess, since that’s pretty much the only way I can think of to do it. Might not work or perform very well, though, so it’d take testing and tinkering.

Six? You almost never see the bottom of the skybox.

Doesn’t mean we should neglect it :<.

You are right, though - we are never intended to see the bottom of the skybox itself, since that will more than likely be a mapping error rather than an intended feature.

That’s a good idea! I’ll have a go.


Just been looking at those sweps, they are really awesome…


It’s not like one render target is going to make that much of a difference.


This is a lot less simple than I thought it would be, however, using this method means I can draw all sorts of things on the skybox itself!

People should be able to ‘order’ advertising in the sky on the RP server!

Or even better, animated clouds!


I don’t know why it’s not working…

The textures seem to be drawn at a lower quality… And the top and bottom textures are being drawn in the wrong places.

See here:

If I were to take a guess, I would think the skyboxes were drawn at the same resolution every time, based on the starting resolution of the skybox associated with the map. So I’m going to guess your “Juiced Servers” logo is a different resolution.

It’s all an assumption and a possibility for what could be happening, so don’t take my word for it, it’s just something to give you an idea on stuff to check.

Are the render targets the correct resolution, so that they match with the skyboxes?

The skyboxes are 1024x1024, the render targets are 1024x1024 and the textures are being drawn onto the rendertarget at 1024x1024.