Here is how to determine whether the party is underwater: if they are in a tile with the underwater flag (such as forest_underwater), and their world Y position is below -0.6, they are underwater. Otherwise, they are not.
WaterSurfaceComponent, WaterSurfaceMeshComponent, etc. are strictly visual. They have no effects on gameplay. You cannot change the -0.6 water height, to my knowledge.
Code: Select all
defineObject{
name = "water_surface_example",
baseObject = "base_floor_decoration",
components = {
{ -- All default values are shown here
-- updates global reflection and refraction maps
class = "WaterSurface",
-- This vector is the color of the fog that appears while the
-- party is underwater. Values above 1 have the same effect as
-- 1, values below 0 and NaN have the same effect as 0.
fogColor = vec(0,0,0),
-- This is how "dense" the fog is, i.e. how quickly it becomes
-- completely opaque. I am not sure how this is scaled; a value
-- of 0 (or lower, or NaN) disables the fog entirely, but a
-- value of 1 does not make the fog reach full opacity immediately
-- (it takes about 3 meters from the camera to do so).
--
-- Interestingly, fog seems to require that a WaterSurfaceMesh be
-- present on the object, otherwise it doesn't do anything.
fogDensity = 0,
-- planeY is the offset of the reflection plane (the plane
-- about which reflections are calculated). Logically, this
-- should be the same as the y position of the WaterSurfaceMesh
-- or whatever other model you're using with the water material,
-- but there are reasons to set it to other values (it's actually
-- set to other values often in the standard assets).
planeY = -1.2,
-- This vector modifies the color of the reflected scene.
-- Negative values work.
reflectionColor = vec(1,1,1),
-- This vector modifies the color of the refracted scene. (The
-- refracted scene means the objects that are *beneath*
-- the water.) Negative values work.
refractionColor = vec(1,1,1),
},
{ -- These aren't defaults
-- builds a continuous mesh from underwater tiles
class = "WaterSurfaceMesh",
-- Material used while the party is above water.
material = "water_surface_calm",
-- Material used while the party is underwater.
underwaterMaterial = "water_surface_underwater",
-- This is how you change the "water level"; by offsetting the
-- WaterSurfaceMeshComponent. Remember to adjust the
-- WaterSurfaceComponent's planeY value. Also, be aware that
-- positive y offsets can cause very strange behaviour with
--
offset = vec(0,-1,0),
},
},
dontAdjustHeight = true,
editorIcon = 264,
}
WaterSurfaceMeshComponent:getWaterLevel() returns the y position of the mesh. This is not synonymous with the y component of the WaterSurfaceMeshComponent's offset; for example, if you let the mesh generate and then move the parent object up by 1 meter, the mesh will move up by 1 meter, and the y component of its offset is the same, but the water level has increased by 1 meter and getWaterLevel() will reflect that.
There's no setWaterLevel() but you don't really need that because you can just change the offset instead.
I have seen people place more than one WaterSurfaceComponent on one level. Don't do that. It doesn't work properly. There should be only one WaterSurfaceComponent on a level at a time. Removing one WaterSurfaceComponent and replacing it with another one should be fine, but I haven't tested it very well.
The water shader
The water shader only works on materials named "ocean_water", "water_surface_calm", or "water_surface_underwater". (All three names seem to behave identically otherwise). Other material names will give bad, useless results.
So if you want to use more than 3 water materials in one mod, take advantage of MaterialEx:setTexture() and MaterialEx:setParam() to change those 3 materials dynamically.
Remember that you need to set the textures/params whenever the game is reloaded, because they revert to their defaults in the defineMaterial table upon reloading.
If you need more than 3 different water materials to be visible to the player at once, then you're out of luck, but I doubt you will ever need that.
Code: Select all
defineMaterial{
name = "ocean_water",
shader = "ocean_water",
diffuseMap = "assets/textures/env/ocean_foam_dif.tga",
normalMap = "assets/textures/env/ocean_normal.tga",
displacementMap = "assets/textures/env/ocean_disp.tga",
doubleSided = false,
lighting = true,
alphaTest = false,
blendMode = "Translucent",
textureAddressMode = "Wrap",
glossiness = 80,
depthBias = 0,
texOffset = 0,
foamOffset = 0,
foamAmount = 1,
waveAmplitude = 50,
onUpdate = function(self, time)
self:setParam("texOffset", time*0.2)
end,
}
I don't think the lighting = true part will ever matter unless you change the blendMode to "Opaque" (and doing so is pretty much useless). Yes, the shader "supports" all blend modes, although most of them aren't useful in this context.
Parameters you can change with setParam() here that will have a useful effect: texOffset, foamOffset, foamAmount, waveAmplitude.
The colour of the foam is determined by the sky's atmospheric colour (which you can change, but it's hard; see the Sky section below). foamAmount basically multiplies the colour of the diffuse map. So at 0, it's completely invisible, at -1 it inverts the colour, and at math.huge or -math.huge it turns the entire water surface almost completely black or white. At NaN it turns it completely black.
waveAmplitude is the degree to which the vertices of the mesh are permuted to make waves. It interacts oddly with the y position of the mesh (and heightmaps?), so watch out for that.
Changing the normal and displacement maps is a good way to make water look like different materials, such as a sheet of ice or a mirror.
Remember that you can use the water materials on custom models, not just WaterSurfaceMeshComponents. This is how the ocean water is done in the standard assets; ocean_water.model is
a large rectangle split into 80,886 vertices so that the shader makes good-looking waves in it, and the vertices are denser in the parts of the rectangle that are close to the shore,
giving higher-resolution waves. Also note that the beach_ground_water tile doesn't have the underwater flag; it just has a splashy move sound and the "water" automap tile.
WaterSurfaceMeshComponent only builds its mesh on tiles that do have the "underwater" flag. Suppose you want to exploit it to make a "flooded" dungeon where all the floor is under
a shallow layer of water, but the party doesn't actually go underwater. You'd use tiles like this:
Code: Select all
defineTile{
name = "dungeon_floor_flooded",
editorIcon = 192,
color = {120,120,180,255},
builder = "dungeon",
floor = {
"dungeon_floor_dirt_01", 1,
},
ceiling = {
"dungeon_ceiling", 1,
},
wall = {
"dungeon_wall_01", 35,
"dungeon_wall_02", 35,
"dungeon_wall_drain", 2,
},
pillar = {
"dungeon_pillar", 1,
},
ceilingEdgeVariations = true,
ceilingShaft = "dungeon_ceiling_shaft",
underwater = true, -- so that WaterSurfaceMeshComponent builds the mesh over it
moveSound = "party_move_wade",
}
and a WaterSurfaceMeshComponent like this:
Code: Select all
defineObject{
name = "water_surface_flooded_dungeon",
baseObject = "base_floor_decoration",
components = {
{
class = "WaterSurface",
planeY = 0.4, -- Notice that the planeY is the same as the y offset of the mesh
reflectionColor = vec(0.77, 0.9, 1.0) * 0.9,
refractionColor = vec(1,1,1),
},
{
class = "WaterSurfaceMesh",
material = "water_surface_calm",
underwaterMaterial = "water_surface_underwater",
offset = vec(0,0.4,0),
},
},
dontAdjustHeight = true,
editorIcon = 264,
}
and a material definition to fix the wave amplitude (default is way too high for this height):
Code: Select all
defineMaterial{
name = "water_surface_calm",
shader = "ocean_water",
diffuseMap = "assets/textures/env/ocean_foam_dif.tga",
normalMap = "assets/textures/env/ocean_normal.tga",
displacementMap = "assets/textures/env/ocean_disp.tga",
doubleSided = false,
lighting = true,
alphaTest = false,
blendMode = "Translucent",
textureAddressMode = "Wrap",
glossiness = 80,
depthBias = 0,
texOffset = 0,
foamOffset = 0,
foamAmount = 0,
waveAmplitude = 0.04,
onUpdate = function(self, time)
self:setParam("texOffset", time*0.03)
end,
}
and then you would not allow the party or monsters to go below -0.6 world y position (which is easy to do, just don't use floor elevations below 0). The result would look like this:
http://i.imgur.com/hJpzz1r.jpg