Yes, the sprites are normal mapped. It is basically just a Deferred Renderer, but being restricted to pure 2D graphics gives you quite some freedom to fool around with the data.
The renderer does:
- one pass to render out diffuse
- one pass to render out screen-space normals
- one pass to render out "extra" data: R height, G shadow density, B monochrom specular intensity
- two passes to calculate ambient occlusion from the per-pixel heights
- one pass per light to render out the per-pixel heights (downscaled 1:4)
- four passes per light to calculate the per-pixel shadow heights (downscaled as well)
- one pass to accumulate the lighting from all lights - default procedure with dot3 against screen-space normal while comparing the per-pixel height with the light's shadow height to determine if the pixel is in shadow or not
- one fullscreen pass to add ambient light modulated with the ambient occlusion, gamma-correct the resulting per-pixel light and multiply the result with the diffuse and add the specular texture
- additional pass to render self-illumimated graphics
- send everything though post processing, which is currently just a vignette and some colour grading
So it's actually just a simple dot3 normal mapping, but gamma correction, ambient occlusion and doing the lighting calculations with real 3D coordinates (Z coming from per-pixel height) goes a long way for achieving believable lighting. Don't underestimate the effect of a proper per-pixel height in all of your graphics! It might seem unimportant because it's invisible under an orthogonal projection, but aside from the shadows it also helps a lot in lighting calculations. Especially narrow spot lights profit a lot from correct 3D positioning of the pixels to be lit. It looks great when the player glances around with his spotlight and furniture in front of him lights up that otherwise would stay dark.