Mapping Bsp Files With Net Radiant

Using a 3D engine for creating a game is a lot different from making a mod to an existing game. Not only you have complete control over what is displayed on every frame (which is good), but you must also program from scratch the core of your game: logic, sound, graphics, GUI... and levels (which is good too, only hard). Making a mod allows you to skip this part and open a map editor directly. But a 3D engine doesn't usually have one.

After finishing Irrlicht's tutorials my thoughts were the same as of many people in this forum (or so it seems): "how do I make a map?" Life was so beautiful when I used Valve Hammer (formerly Worldcraft), UnrealED or Radiant. But those where fully functional games I was making levels for. Irrlicht supported a lot of formats but left me complete freedom to choose my own, and implement it. But that is tiring, so I studied a way to use an existing map editor with Irrlicht.

Because I'm neither rich nor a Windows user, some options were inmediatly discarded (like irrEdit). The Blender irrscene export plugin looked good, but the XML format was slow. Also, mapping in Blender sucks Smile. I wanted my old editors back! So, because Irrlicht supported Quake 3's BSP format, the choice was easy: Radiant.

I managed to install it in Linux and experimented a bit with the existing game packs. Finally I came up with a working game pack for Irrlicht. Now I'm able to use Radiant for Irrlicht mapping, using my own textures and entities. So I decided to share this in case some of you find it useful (though I doubt it).

Irrlicht and NetRadiant: a story of love (or tutorial for short)

1. Irrlicht

Well, the first thing is obviously to get Irrlicht. You can use your own method. I wrote a script because I'm lazy, you can find it in . Some dude complained it fails with relative paths. So use absolute ones or do it manually, whatever you like.

Now, we need to get a working copy of the editor.

2. The editor

GtkRadiant (the old good editor) is no longer maintained, therefore some forks have appeared. ZeroRadiant's development is as slow as a snail walking sidewards, and it's also more difficult to configure, but I managed to install it in both Windows and Linux, and it's a damn good editor if you're interested in doing mods or official maps.

On the other hand, NetRadiant is easier to configure and the interface I like most. Nice icons duh. It's also smaller, about 180 MB versus the massive 1 GB of ZR with all gamepacks downloaded.

So let's begin. This tutorial is Linux-oriented. I spent a whole day trying to compile NR in Windows and succeeded, but it's not worth it, believe me. No one develops for Windows. And whoever does will burn in hell along with Hitler, Stalin and the people that use the Carve tool to build their Half-Life maps.

I wrote another script for installing it, but the steps are few and you may learn something, so we'll do it manually. This way no one will complain about my shitty scripting, too. Open a console and install all the dependencies, in Debian or Ubuntu you'd issue:

Code: sudo aptitude install build-essential subversion git-core git-svn scons libsdl-dev libgtk2.0-dev libgtkglext1-dev libxml2-dev zlib1g-dev libpng-dev -y

Wait patiently until it's finished. Now we can recover the last version of the source. Type:

Code: git clone git:// <folder>

With "<folder>" being the location you want NetRadiant to be installed. It'l take just a minute to retrieve the copy (17.5 MB). Now open the folder and familiarize yourself with it. You can read the COMPILING file for detailed instructions, but there's no need to. Just open a console here (or cd from the previous) and do:

Code: sh ./

Yes, they use scripts too Cool. This one will ask you whether or not to download a series of gamepacks. No need to download them all, OpenArena's will suffice, When finished, it's time to compile it:

Code: make

You can append "-jX" to speed up compiling, "X" being 2 times your number of processors or cores. It doesn't matter much here, but with bigger software sources you'll thank Odin that this switch exists. My PC took 30 seconds with "-j8" and 5 minutes without. Anyway, wait for it to compile.

Look at the folder again, several things spawned. The only important subfolder is "install/", this is where the binary was placed. Move there and type:

Code: ./radiant.x86

The following dialog will appear, select "OpenArena" from the game list and accept. The main window will show up. If you are new to Radiant, have no fear. If you used it before, welcome back home. Select "Edit/Preferences" or press P, and configure it as you see fit. The options are all self-explanatory, but in case of doubt, leave it unchanged.

The default interface:

3. OpenArena test map

You can entirely skip this section, but you're advised NOT to do so, as it will teach you some concepts. We're going to build a very simple test map for OpenArena, and try to load it in Irrlicht. So you obviously need the game.

Either download it from the web and unpack to the desired folder, or install it from the repositories. In Ubuntu or Debian:

Code: sudo aptitude install openarena -y

If you chose the last option, there is one additional step, due to the fact that the game executable and the resources were put in different locations. So do this:

Code: sudo ln -s /usr/share/games/openarena/baseoa /usr/games/baseoa

Now open again NetRadiant in case you closed it. See how the texture browser is empty? (if you changed the default window layout in the preferences, select "View/Texture Browser" or press T). This is because the folder Radiant believes the game to be in isn't the right one. Open the preferences (P), go to "Settings/Path" and change "Engine Path" from "/usr/local/games/" to "/usr/games/" (if you installed the game through the package manager), or the folder you downloaded it to. If you are unsure, see that it contains a "baseoa/" subfolder.

Accept and reload NR (close and launch again). Open the Texture Browser once more (T). If you did it right, the editor should now offer you a bunch of textures, classified by groups. Good, now we can start mapping.

The purpose of this tutorial is not to teach you how to map. There are other good references on the net, like . It's not hard and it will change your life, trust me. Anyway, just create a simple "Hello Room" with a light and an "info_player_start" entity right in the middle, no need to watch the r_speeds here Razz. Use any texture for the walls, just try to remember its name, as we'll need it later (I personally chose "base/base_wall/bluemetal3b"). Oh, and be sure to use only plain, normal textures (I'd suggest to use the same for every brush), not special textures with shader definitions (lava, water, etc). This does not include the special "caulk" texture used to turn a surface invisible.

Save the map (Ctrl+S). It will default to "~/.openarena/baseoa/maps/" (yes, in your home folder). Rename it to "" and accept. Now compile it. Select "Build/Q3Map2: (final) BSP -meta, -vis, -light -fast -filter -super 2" or any other valid option. Right after clicking it, open the output console ("View/Console View" or press O) and wait until it's finished. If an error occurs, your map design is probably not valid, i.e. a leak to the void or something.

By the way, you can open your .map files with a text editor if you want, to study the level structure. Yes, they are text files, just like Half-Life's MAP files (no surprise here, as it's based in Quake 2's engine). Inside them, the level's brushes are defined via the coordinates and texture mapping. The entities, too. If you are a real hacker, you could even do your mapping with just your keyboard, and then manually compile it from the command line using q3map2. But it's very difficult unless your name is John Carmack. Here is my test map in plain text, if you feel like copypasting instead of building it from scratch. Save it under "" in "~/.openarena/baseoa/maps/" and you're a go.

Quote: // entity 0 { "classname" "worldspawn" // brush 0 { ( 128 120 -64 ) ( 128 -136 -64 ) ( -128 120 -64 ) base_wall/bluemetal3b 0 0 0 0.5 0.5 0 0 0 ( 128 128 56 ) ( -128 128 56 ) ( 128 128 -72 ) common/caulk 0 0 0 0.5 0.5 0 0 0 ( 128 128 56 ) ( 128 128 -72 ) ( 128 -128 56 ) common/caulk 0 0 0 0.5 0.5 0 0 0 ( -128 -128 -72 ) ( 128 -128 -72 ) ( -128 128 -72 ) common/caulk 0 0 0 0.5 0.5 0 0 0 ( -128 -128 -72 ) ( -128 -128 56 ) ( 128 -128 -72 ) common/caulk 0 0 0 0.5 0.5 0 0 0 ( -128 -128 -72 ) ( -128 128 -72 ) ( -128 -128 56 ) common/caulk 0 0 0 0.5 0.5 0 0 0 } // brush 1 { ( 128 -128 192 ) ( 128 -136 192 ) ( -128 -128 192 ) common/caulk 0 0 0 0.5 0.5 0 0 0 ( 128 -128 192 ) ( -128 -128 192 ) ( 128 -128 -64 ) base_wall/bluemetal3b 0 0 0 0.5 0.5 0 0 0 ( 128 -128 192 ) ( 128 -128 -64 ) ( 128 -136 192 ) common/caulk 0 0 0 0.5 0.5 0 0 0 ( -128 -136 -64 ) ( 128 -136 -64 ) ( -128 -128 -64 ) common/caulk 0 0 0 0.5 0.5 0 0 0 ( -128 -136 -64 ) ( -128 -136 192 ) ( 128 -136 -64 ) common/caulk 0 0 0 0.5 0.5 0 0 0 ( -128 -136 -64 ) ( -128 -128 -64 ) ( -128 -136 192 ) common/caulk 0 0 0 0.5 0.5 0 0 0 } // brush 2 { ( 128 136 192 ) ( 128 128 192 ) ( -128 136 192 ) common/caulk 0 0 0 0.5 0.5 0 0 0 ( 128 136 192 ) ( -128 136 192 ) ( 128 136 -64 ) common/caulk 0 0 0 0.5 0.5 0 0 0 ( 128 136 192 ) ( 128 136 -64 ) ( 128 128 192 ) common/caulk 0 0 0 0.5 0.5 0 0 0 ( -128 128 -64 ) ( 128 128 -64 ) ( -128 136 -64 ) common/caulk 0 0 0 0.5 0.5 0 0 0 ( -128 128 -64 ) ( -128 128 192 ) ( 128 128 -64 ) base_wall/bluemetal3b 0 0 0 0.5 0.5 0 0 0 ( -128 128 -64 ) ( -128 136 -64 ) ( -128 128 192 ) common/caulk 0 0 0 0.5 0.5 0 0 0 } // brush 3 { ( 128 128 200 ) ( 128 -128 200 ) ( -128 128 200 ) common/caulk 0 0 0 0.5 0.5 0 0 0 ( 128 128 200 ) ( -128 128 200 ) ( 128 128 192 ) common/caulk 0 0 0 0.5 0.5 0 0 0 ( 128 128 200 ) ( 128 128 192 ) ( 128 -128 200 ) common/caulk 0 0 0 0.5 0.5 0 0 0 ( -128 -128 192 ) ( 128 -128 192 ) ( -128 128 192 ) base_wall/bluemetal3b 0 0 0 0.5 0.5 0 0 0 ( -128 -128 192 ) ( -128 -128 200 ) ( 128 -128 192 ) common/caulk 0 0 0 0.5 0.5 0 0 0 ( -128 -128 192 ) ( -128 128 192 ) ( -128 -128 200 ) common/caulk 0 0 0 0.5 0.5 0 0 0 } // brush 4 { ( -136 128 192 ) ( -136 -128 192 ) ( -144 128 192 ) common/caulk 0 0 0 0.5 0.5 0 0 0 ( -128 128 184 ) ( -136 128 184 ) ( -128 128 -64 ) common/caulk 0 0 0 0.5 0.5 0 0 0 ( -128 128 184 ) ( -128 128 -64 ) ( -128 -128 184 ) base_wall/bluemetal3b 0 0 0 0.5 0.5 0 0 0 ( -136 -128 -64 ) ( -128 -128 -64 ) ( -136 128 -64 ) common/caulk 0 0 0 0.5 0.5 0 0 0 ( -136 -128 -64 ) ( -136 -128 184 ) ( -128 -128 -64 ) common/caulk 0 0 0 0.5 0.5 0 0 0 ( -136 -128 -64 ) ( -136 128 -64 ) ( -136 -128 184 ) common/caulk 0 0 0 0.5 0.5 0 0 0 } // brush 5 { ( 136 128 192 ) ( 136 -128 192 ) ( 128 128 192 ) common/caulk 0 0 0 0.5 0.5 0 0 0 ( 136 128 192 ) ( 128 128 192 ) ( 136 128 -64 ) common/caulk 0 0 0 0.5 0.5 0 0 0 ( 136 128 192 ) ( 136 128 -64 ) ( 136 -128 192 ) common/caulk 0 0 0 0.5 0.5 0 0 0 ( 128 -128 -64 ) ( 136 -128 -64 ) ( 128 128 -64 ) common/caulk 0 0 0 0.5 0.5 0 0 0 ( 128 -128 -64 ) ( 128 -128 192 ) ( 136 -128 -64 ) common/caulk 0 0 0 0.5 0.5 0 0 0 ( 128 -128 -64 ) ( 128 128 -64 ) ( 128 -128 192 ) base_wall/bluemetal3b 0 0 0 0.5 0.5 0 0 0 } } // entity 1 { "classname" "light" "origin" "0.000000 0.000000 184.000000" "light" "300" } // entity 2 { "classname" "info_player_start" "origin" "0.000000 0.000000 -32.000000" }

If it successfully compiled, you can close Radiant for now. We're gonna test the map in OpenArena before trying to open it in Irrlicht. So launch the game, open the console (with the key that is to the left of the number 1, and above TAB), and type:

Code: map test

If it complains something about map not found, enter the following:

Code: sv_pure 0

and retry. You should see something like this:

OK, the map works in the game. But what about Irrlicht? Let's find out. We're gonna use the code from the 2nd tutorial (02.Quake3Map), as it's quite simple and we don't need to test shader capabilities right now, the bare BSP mesh will do (and the process would be tiring otherwise). So move to "<irrlicht>/examples/02.Quake3Map/" and edit the "main.cpp" file. Then replace

Quote: device->getFileSystem()->addZipFileArchive("../../media/map-20kdm2.pk3");


Code: device->getFileSystem()->addZipFileArchive("../../media/test.pk3");


Quote: scene::IAnimatedMesh* mesh = smgr->getMesh("20kdm2.bsp");


Code: scene::IAnimatedMesh* mesh = smgr->getMesh("test.bsp");

IMPORTANT: also, comment out the lines that say:

Quote: if (node) node->setPosition(core::vector3df(-1300,-144,-1249));

because otherwise the map will be placed too far away from the camera at startup, and it may look like nothing worked.

Save and close. Open a console in this folder and type the following to compile the modified example:

Code: make

Now we need to compose our own "test.pk3" package. Open the existing "map-20kdm2.pk3" found in "<irrlicht>/media/" and take a look. You can see 5 folders there, but we just need 2, as we ain't using any shaders (you didn't use special textures, right?). So, go to any location you want and create two new folders: "maps/" and "textures/". Then copy the compiled "test.bsp" map (you remember where it is, don't you?) into our "maps/" folder.

The next step is a bit tedious, but necessary. We need to place in the other folder, "textures/", any image files that our map uses. I chose "base/base_wall/bluemetal3b" for the walls, floor and ceiling, but where is it? You got it, right in the OpenArena's resources folder. So go to "/usr/games/baseoa/" (in case you downloaded it from internet, it will be a different location). Every .pkg the game needs is placed there.

Open "pak4-textures.pk3" with your favourite file explorer, then navigate to "textures/" and "base_wall/" (or whatever texture group/s you used). There's my sweet "bluemetal3b.jpg". We need to replicate this "textures/" folder, but containing ONLY the texture files our map used (you'd waste disk space uselessly otherwise). I can think of several ways of doing this, but the one I like most is to go up one level, extract the whole "base_wall/" folder in my own "textures/" folder and then delete all image files except for "bluemetal3b.jpg".

That's it. Now select both folders ("maps/" and "textures/") and compress them as "". Rename it to "test.pk3" (because PK3 files are just ZIP files with another extension). Finally, put it in the "<irrlicht>/media/" folder, next to "map-20kdm2.pk3".

Open a console in "<irrlicht>/bin/Linux/" (where the examples' binaries are) and launch the example:

Code: ./02.Quake3Map

If you did everything as I told you, you should see this:

And if you don't, you probably messed up with the textures. Read through the console output and look for error messages.

4. Irrlicht game pack

Of course, this was just a small test. For a real game, depending on OpenArena to be installed would suck. So we'll try to find a way to make NetRadiant work with Irrlicht like it was another supported game. We need to create a Radiant profile.

Close everything and go to the NR folder, then the "install/" subfolder. Here you will see, among many other things, two subfolders called "games/" (containing an "" file), and "" (yes, a folder named like the previous file). We must create our own "<ZR>/install/games/" file and the corresponding "<ZR>/install/" subfolder. So why not copy the existing OpenArena's ones and use them as reference?

Do it so, and rename them as I told you. After that, I'm gonna explain what's each file for.

Open "<NR>/install/games/" with a text editor (I use Gedit, but even Vim will do Wink ). It's a XML file containing parameters and the default values. The names are self-explanatory, and not all of them have to be changed. Edit the following:

    "name" (change it to "Irrlicht")
    "enginepath_linux" (set it to the folder you want to create your Irrlicht game in, like "/home/john/mygame/")
    "prefix" (set it to ".irrlicht"; mind the initial dot)
    "basegame" (set it to the name of the subfolder you'll place your game's resources in. If it's "/home/john/mygame/resources/", then enter "resources"
    "basegamename" and "knowngamename" (set it both to "Irrlicht")

The rest of the parameters you'd change when making a real game, but it's not needed now. Just a quick tip: I've discovered that NetRadiant may fail at startup if you add your own "rare" file types to the "modeltypes" parameter, whining about the patch inspector or something. So stick with the predefined types and use another method to add models into your map, like entities (more on this later).

Save and close; it's time to gaze into "<ZR>/install/". Go there and you'll see two XML files and a subfolder. Open the first file, "default_build_menu.xml". This file contains the build options that are shown in NetRadiant's "Build" menu. along with the commands that are executed for each one. You're lucky because they all serve well our purpose, so no change is needed, I just wanted you to know where they are in case you're a q3map2 (Quake 3's BSP compiler) expert and want to tamper with them.

Open now "game.xlink". It contains a number of links that will be shown in the "Help" menu, under a category named like the "name" (no sh*t!) parameter you set in "". When clicked, the links will open your default internet browser and load that URL. Once again, you don't need to change anything for now.

Only a folder named "baseoa/" remains, but there's a problem. We set our "basegame" to be "resources" instead of "baseoa", remember? (well, at least I did). So rename the folder to "resources/" before opening it.

Inside you'll discover 5 files. 3 of them can be safely deleted. First, the ones whose names end in "-ta", because they are intended for Quake 3's expansion, Team Arena. Second, another file named "entities.def", which contains the old Quake 3's entity definitions. Remove them and open "default_shaderlist.txt". It contains a list of shader definition files to be loaded at startup.

What is a shader? Well, not all textures in Quake 3 (or OpenArena) are common walls or floors. Some have special properties that can interact with the BSP compiler, like the "hint" texture, which can manually create BSP cuts where we want them. And some others can interact with the game in the way that they are rendered (the invisible "caulk") or behave, like surfaces that move (skies), that inflict damage (acid, lava), or that produce some sound when you walk over them (a metal grate).

These properties are defined in .shader files placed usually in a "scripts/" subfolder (inside the resources folder, like "baseoa/scripts/"), and named like the group of textures they are defining (like "common.shader", which contains "caulk", "hint", "invisible", "mirror1" and other general purpose textures). OpenArena's shader files can be found in "/usr/games/baseoa/pak0.pk3/scripts/" (if you used the package manager to install the game).

At startup, the editor will inspect the "default_shaderlist.txt" file. For every name it finds, a file named "resources/scripts/name.shader" will be parsed for texture definitions. Remember that common textures don't need to appear in a .shader file if they don't have special properties. For now, you can delete ALL lines, but when making a real map it would be wise to include at least "common", as they are very helpful. Save and continue.

"entities.ent" is a massive 2078-line file that contains OpenArena's entity definitions. An entity is a thing that is not a common geometry brush, and that can be placed inside your level for achieving some purpose. That is, some kind of "logical" being instead of a physical one. Lights are entities, player respawn points are entities, buttons, weapons, ammo and moving doors or elevators are all entities. The game will query for a list of entities when the map is loaded, and will do different things according to the type and properties. Also, some entities can interact with the compiler, like the lights, who are used to calculate the static lighting of the level.

Thanks to this file, you'll be able to create your own entities for your games, and place them using the editor. Then, you'd query the entity list using an Irrlicht function, and do whatever you want depending on the type or properties. If building the level geometry was the first half of the job, placing entities is the second.

After this explanation, carefully delete ALL entity definitions except for: "light" (not to be mistaken with "lighJunior"), "info_player_start" and "worldspawn".

Why "worldspawn"? You surely don't remember placing it in the level... because you didn't. The Worldspawn is a special entity that is automatically placed in a new level by the editor. It contains some world properties like the name, the music track, the gravity or the sunlight. It can't be selected from the editor's GUI, but it's accessed through NetRadiant's menus instead ("View/Entity Inspector" or pressing N).

That's it. Close the file and now make sure you have created your game's folder. E.g., "/home/john/mygame/". Create there a subfolder named "resources/" (or whatever "basegame" you chose). And finally, inside this subfolder create another one named "textures/".

Launch NetRadiant and this time choose "Irrlicht" from the drop-down list (I entered that as "name" in my "" file). Accept and you'll find that you must configure the program apart for each game. Do it now if you want. Save the default, empty map. It's saved as "~/.irrlicht/resources/maps/", but you can change the "prefix" parameter (in "") to be your game folder instead of ".irrlicht", and the map name would be defaulted to "/home/john/mygame/resources/maps/". I prefer to use ".irrlicht" instead, because this way my test maps don't get mixed up with the finished ones. Anyway, remember to enter "" as name.

The first thing you'd want is to use your own textures for your map's geometry. We can't go out there stealing OpenArena's textures, can we? So I made a very shitty texture (hey, I'm a programmer, not an artist!) called "mytexture.tga" and put it in a folder called "nightmaren/" inside my game's textures folder ("/home/john/mygame/resources/textures/"). I reloaded NetRadiant and opened the Texture Browser, to confirm that it automatically loaded the file.

Remember, your textures NEED to be placed in a subfolder (preferably named after the category it belongs to) within "textures/", just storing them in the root "textures/" folder won't do any good. You can compress the whole textures folder in a pk3 package if you want, NR will extract it, as it's one of the formats you specified in the "archivetypes" parameter of the "" file.

And another note: don't bother creating BMP or PNG textures, as Irrlicht's loader doesn't support them (though NetRadiant will), and any brush with that texture applied won't be rendered at all. It took me a day of testing to reach this conclusion, and I don't want you to fall in the same trap.

Keep creating your own textures and expanding your map. When you're happy with it, compile, and put it in a PK3 file like I explained in the previous section. Don't forget to include all used textures. Then, try loading it from Irrlicht using the code from the second example. That's all, now you are able to create BSP levels for Irrlicht with your own textures. Good mapping!

5. Advanced subjects

5.1 Shaders

I've already made clear (or so I hope) what a Q3 shader is and what they're for. Only we haven't used one yet. Of course, writing an acid damage or a metal footstep shader for a texture would be useless because the logical behaviour must be implemented within the game (unless we loaded the level in OpenArena, but we're not). But we can use some other interesting effects, like movement or lighting.

5.1.1 Light surfaces

If you take a look at "/usr/games/baseoa/pak0.pk3/scripts/oalite.shader" you'll understand what I mean. We'll now create a texture shader that will make that surface emit light, so it's taken account by the compiler.

Create a new file in your "<game>/resources/scripts/" folder named "nightmaren.shader". Edit it and add the following lines:

Code: textures/nightmaren/mytexture

      q3map_surfacelight 500

Replace "nightmaren/mytexture" with your own texture's group and name. Done? OK, remember that "default_shaderlist.txt" file? It was inside your profile folder. Open it and append a line with this:

Code: nightmaren

Now start NetRadiant and create a new map. Apply this texture to the surface you like most, but do NOT add any light entities. Compile, put it in a PK3 and open it from Irrlicht as we've done before. My result:

Mon Dieu! The surface is now emitting light, and when properly used, it can bring new life to your levels.

If your map is still as dark as the whole Kennedy affair, do not despair, there is one solution. When you compile a map with NetRadiant, the program copies your "default_shaderlist,txt" file (the one we appended "nightmaren" to) into the "<game>/resources/scripts/" folder, renaming it to "shaderlist.txt". Then, the compiler is fed this info so it knows about your shaders. Problem is, if this file is already present there, the copy isn't made even if the original was altered. If it's the case, delete the second file, "shaderlist.txt", reopen the editor and compile again.

Another note: the lighting in your levels is prone to change depending of the compiling command you choose. The second "final" option has a "-bounce 20" switch. This means that the compiler will take into account the bouncing of the light rays over your surfaces, up to 20 times, and usually results in a brighter map.

5.1.2 Moving surfaces

No, the surfaces won't really move, just the textures on them. But this requires additional coding with Irrlicht. Don't start running yet, because we already have a perfectly working example: 16.Quake3MapShader. Go to "<irrlicht>/examples/16.Quake3MapShader/" and open the "main.cpp" file. Change the following lines:


  2. define QUAKE3_STORAGE_FORMAT addZipFileArchive
  3. define QUAKE3_STORAGE_1 "../../media/map-20kdm2.pk3"
  4. define QUAKE3_MAP_NAME "maps/20kdm2.bsp"
  5. endif



      #define QUAKE3_STORAGE_FORMAT   addZipFileArchive
      #define QUAKE3_STORAGE_1   "../../media/test.pk3"
      #define QUAKE3_MAP_NAME         "maps/test.bsp"

Save, open a terminal there and compile with:

Code: make

Try it if you want by moving to "<irrlicht>/bin/Linux/" and running:

Code: ./16.Quake3MapShader

It will load the last map we tested, you'll notice no big difference apart from the darker background color. Now reopen "<game>/<resources>/<scripts>/nightmaren.shader" and change the contents to this (changing the names so it fits your texture, of course):

Code: textures/nightmaren/mytexture

      q3map_surfacelight 500
         map textures/nightmaren/mytexture.tga
         tcMod scroll 0.1 0.1

Open NR, create a new brush in the middle of your map and apply the texture to it. Compile. But this time we have one additional step, as now Irrlicht needs the shader information to properly render the moving effect. So, before compressing the "maps/" and "textures/" folders into a PK3 file, create a new "scripts/" folder and copy the "nightmaren.shader" file inside it.

OK, then create the "test.pk3" file as usual and run the example. Gott in Himmel! Not only will the surface emit light, but the texture will scroll both horizontally and vertically on it, though I can't show you that in a screenshot. There is a myriad of different effects available to you in the Q3's shader documentation. You can read a fantastic copy here: .

For example, you can make liquid surfaces effectively move back and forth following a sine wave, or turn a surface invisible, visible from both sides (turning of both back and front culling) or passable. Just read through that documentation and find the effect you want. The parameters can also change how a surface is treated during the compiling phase, remember.

I do remember that in the existing "map-20kdm2.pk3" there was a nice lava texture with a shader. This is a totally different effect that will in fact displace the vertices of a surface, following a sine wave. Change the shader to this:

Code: textures/nightmaren/mytexture

      q3map_surfacelight 500
      deformVertexes wave 100 sin 3 2 .1 0.1
         map textures/nightmaren/mytexture.tga
         tcMod scroll 0.1 0.1

Apply the texture to a brush in the middle of the room, compile and run (don't forget to put the modified "nightmaren.shader" in the PK3). Here is my example, I decided to keep using my shitty texture, but any other would do. As you can see, the surfaces seem to "float" gradually (in a real map, you'd only use the upper surface for water or lava):

Before moving to the next section, let me give you a piece of advice: name the .shader files EXACTLY as the folder which contains the textures that file declares. This is so by agreement, and conventions are a good thing to follow if you want less headaches. So it would be either "scripts/nightmaren.shader" with "textures/nightmaren/mytexture.tga" OR "scripts/anotherthing.shader" with "textures/anotherthing/mytexture.tga", never mixed. Irrlicht can fail to render the effect otherwise!

5.2 Entities

Oh, yes, entities. Such a powerful concept for making our levels better. I left them for the end because they involve a bit of coding, but if you made it till here without problems, then it'll be a piece of cake.

Entities are 99% ingame, and they will vary a lot between different games. They don't have to be associated with a world brush or a 3D model: a simple area damage entity that hurts players while the stay inside the zone of effect is an example. You'll have to carefully think what are you trying to achieve before writing the definition (and handler) of an entity.

Close everything and go back to "entities.ent", in our NetRadiant's profile folder. We removed everything but the most common ones. If you slowly read this file you'll start to grasp the idea. Most definition start with a "<point>" (the Worldspawn uses "<group>") property, that stores several parameters like "name" (the name of the entity as it will appear in the placement menu), "box" (the bounding box that will represent the entity within Radiant) and "color" (the RGB color of this box, each value a real number ranging from 0 -black- to 1 -white-),

Then comes the description, followed by a series of keys (named variables of different types that store some value). Some of them, the ones whose name contains an initial "_", are intended for the map compiler and you'll rarely use them. Each key can store a different type of value, like:

    "integer" (a number)
    "integer3" (a vector containing 3 integers)
    "real" (a number with decimal digits)
    "color" (a RGB value, similar to a real3 because each number goes from 0 to 1)
    "boolean" (a single bit that can be set to 0 for false or 1 for true)
    "flag" (like a boolean, but it can be conveniently switched on and off from the editor)
    "string" (plain text)
    "texture" (surprise, surprise)
    "sound" (I'll never tell you!)
    "target" (to make this entity point to another)

From the original definition files you'll learn how to use all the types and features. But, once defined and placed in a map, how can I access them from Irrlicht? Well my friend, you just have to open and read again the example number 16 (Quake3MapShader):

Quote: if ( mesh ) { quake3::tQ3EntityList &entityList = mesh->getEntityList();

quake3::IEntity search; = "info_player_deathmatch";

s32 index = entityList.binary_search(search); if (index >= 0) { s32 notEndList; do { const quake3::SVarGroup *group = entityList[index].getGroup(1);

u32 parsepos = 0; const core::vector3df pos = quake3::getAsVector3df(group->get("origin"), parsepos);

parsepos = 0; const f32 angle = quake3::getAsFloat(group->get("angle"), parsepos);

core::vector3df target(0.f, 0.f, 1.f); target.rotateXZBy(angle);

camera->setPosition(pos); camera->setTarget(pos + target);

++index; /* notEndList = ( index < (s32) entityList.size () && entityList[index].name == && (device->getTimer()->getRealTime() >> 3 ) & 1 );

  • /

notEndList = index == 2; } while ( notEndList ); } }

ˇAhí va la hostia! Now everything looks clear. That "origin" is a predefined key that stores the position of the entity from the level's origin. And "angle" we already knew of. Using the available Irrlicht functions we can query for all entities in the map and retrieve their properties! Then we'd do anything we wanted to with that info.

I can't wait any longer, let's create our own (simple) entity to see if we got it right. Because the most satisfying task of making a level is to populate it with enemies, we'll define an "enemy_ninja" entity so this 3D model is spawned in every place the entity is... placed Confused .

Append the following to your profile's "entities.ent" file:

Code: <!--




   <point name="enemy_ninja" color="0 0 1" box="-16 -16 -24 16 16 32">
   Enemy ninja spawn location.
   -------- KEYS --------
   <angle key="angle" name="Yaw Angle">Direction in which the ninja will look when spawning in the game.</angle>
   <string key="skin" name="Skin Color">Skin color to be loaded by the game for this new ninja (red or blue).</string>

Open the editor, create a new test level and place an "enemy_ninja" inside (remember, with a right-click). Then select it (SHIFT+left-click) in order to edit the properties ("View/Entity Inspector" or press N). The "Yaw Angle" you can leave as it is, but make sure to enter "blue" (or "red") as "Skin Color" Save and compile.

Assemble a PK3 package as I've trained you to, but don't run the example yet because our ninja won't show up. And it's not like he's shy, but we haven't coded what to do with him instead. Open the "main.cpp" file for the 16th Irrlicht example and change those lines I pasted before for the following ones:

Code: if ( mesh )

      quake3::tQ3EntityList &entityList = mesh->getEntityList();

      quake3::IEntity search; = "enemy_ninja";

      s32 index = entityList.binary_search(search);
      if (index >= 0)
         s32 notEndList;
            const quake3::SVarGroup *group = entityList[index].getGroup(1);

            u32 parsepos = 0;
            const core::vector3df pos =
               quake3::getAsVector3df(group->get("origin"), parsepos);

            parsepos = 0;
            const f32 yaw = quake3::getAsFloat(group->get("angle"), parsepos);

            scene::IMeshSceneNode* ninja =

            if (ninja)
               ninja->setScale(core::vector3df(10.f, 10.f, 10.f));
               ninja->setRotation(core::vector3df(0, yaw, 0));
               ninja->setMaterialFlag(video::EMF_LIGHTING, false);
               ninja->setMaterialFlag(video::EMF_NORMALIZE_NORMALS, true);

               if (group->get("skin").equals_ignore_case("blue"))
                  ninja->setMaterialTexture(0, driver->getTexture("../../media/nskinbl.jpg"));
               else if (group->get("skin").equals_ignore_case("red"))
                  ninja->setMaterialTexture(0, driver->getTexture("../../media/nskinrd.jpg"));



            notEndList = (   index < (s32) entityList.size () &&
                        entityList[index].name == &&
                        (device->getTimer()->getRealTime() >> 3 ) & 1
  • /
            notEndList = index == 2;
         } while ( notEndList );

Compile and run.

I'm going to cum! Everything turned out as I expected. Well. almost. Due to the differences between measures in different 3D formats, we have to play a bit with the position, scaling and rotation until we achieve the results we want. But nothing prevents us now from creating a complete game (well, at least, the levels).

5.3 Terrain

A forum user asked me if he could load a .bmp terrain heightmap directly into NetRadiant. Well, a short answer would be "no", but in fact it can be done using and intermediary tool.

NR has a lot of useful features and several plugins (like a tree planter tool), but none of these "ancient" BSP editors were thought for easy terrain creation, like the more recent Far Cry Sandbox. Typically, the mapper would create a big, planar "grid" brush and start editing the vertices, one by one. The results would be usually a bit blocky, and it's also an exhausting way, not the mention the crappy texturing in very steep surfaces. Here you have some educational links for this method: (making terrain and terraforming with NetRadiant) (rapid terrain creation explanatory video) (terrain texture blending)

Personally I'd create my own map type, some kind of text file that would declare a series of meshes to load (with the corresponding game coordinates), one per line. This way the programmer could combine BSP files for geometry, heightmap files for terrain, skyboxes, detail objects, etc. But hey, this is forum. You ask, I deliver.

5.3.1 Easygen

I warn you: the following part is Windows-only, I've never been able to get the program working with Wine, even after downloading any missing DLL (curse you, mfc42.dll!) and placing it in the program's folder. So if your religion forbids you to load Microsoft's OS in the main memory, just stop reading. So there we go (sigh), Windows XP.

The tool in question is called EasyGen. It's old (2003 latest version) and I don't like it. So I ain't teaching you how to use it in depth. If you want, go to the official page ( and download the ZIP file. No installation required, thank Satan. Extract it in a folder, open it and execute "EasyGen.exe". First-time config time! You can leave everything as it is, except for the 3 empty fields that ask you for some locations. Enter whatever you want, like your Documents folder, in all three. The "Slope max" option can be increased, but anything higher than 60-65 degrees is guaranteed to look worse than a 56-Kbit porn movie.

Watch the main window appear and... close it. You're not prepared yet, as you need a heightmap file and a decent texture to create your terrain. No one is crazy enough to do it manually, and the best options out there are either Terragen or L3DT. Ironically enough, L3DT works like a charm with Wine, and is my favourite one. Go to the web (, download (12.8 MB) and install it.

The guys from Bundysoft are so nice that they provided you with an idiot-proof walkthrough guide the first time you install the program. Take advantage of it and create a new terrain with any setting you find appropriate. And here comes the problem. Q3 maps were good back in 1999 (and they are still today, if you know your limits). But generating good-looking and realistic terrain requires a lot of detail and high resolution imagery. EasyGen supports neither and it's hard to get it right the first time. Be sure to lower the size, and try a couple of times until it looks plausible.

When you have your BMP heightmap, reopen it and import the image using the available button in the upper-left corner (to the right of the "Save" button). The program will load it and show a 3D representation. If it doesn't look good, hesitate not and retry, because you won't like to waste time trying to get q3map2 to compile a monstrosity like that. If you see something with a little resemblance to the next picture, start again unless your computer is a Cray XE6 and you can spare a couple aeons compiling the BSP.

Tired yet? Now comes the worst part: that shiny PNG texture L3DT kindly generated for you, you can send to the trash. With EasyGen, the terrain must be painted manually, and you need the proper terrain textures for doing that. OpenArena includes none, so either you get them from Return to Castle Wolfenstein or Enemy Territory, or create your own according to the mentioned ones.

Finally you'll be able to export a MAP file. The program offers you to automatically surround it with brushes for easily adding a sky, and to insert a spawnpoint so you don't have to wait much for testing. Remember to put the textures you painted the terrain with in the proper places.

As I said, I won't go further on this subject. If you still believe importing an heightmap into NetRadiant is your best choice, take a look at the tutorial in

One last tip: don't (ever!) forget to make your terrain detail (instead of structural), or the insanely high amount of BSP cuts will make you die waiting for it to compile. More info at

6. Conclusion

This is the end, my friend. You emptied my pool of wisdom, and I feel like having a beer. You can check the documentation for Irrlicht's Q3 namespace in . The rest is trying and learning. Dismissed!

I've uploaded most of the files I found useful. If you paid attention, you'll know where and why should them be put. Here is the download link for the tutorial files:

And finally, don't forget to check this links: (installing Irrlicht) (Radiant mapping tutorial) (Quake 3 shader tutorial) (irr::scene::quake3 namespace tutorial) (creating terrain with EasyGen) (the importance of using well detail brushes)