skin deep modding guide 1

getting started

Recently, I’ve been playing the fantastic comedy imsim, Skin Deep by Blendo Games. It’s made in a spinoff of the Doom 3 engine, and has a built-in mod loader on the main menu, so I decided to crack open the game’s existing maps and figure out how to make my own! Read on for the things I found out, and how to get started yourself.

My Experimental Mod

I made a bunch of weird little test thingimajigs in a big boxy map as I was learning how the levels worked. You can see a video of it on YouTube here:

As you can see, the map is full of little text nodes explaining how to make bits of it, so you can download the mod yourself and try editing it yourself! Click here to download the mod.

The Guide

This is a little text guide describing how to set up Skin Deep for modding and how to make your first functioning map.
NOTE: This guide is now outdated. Check out the official Blendo guide on Steam, and the in-game Mod Museum!

Requirements

You will need:

  • Skin Deep (of course)
  • The latest version of DarkRadiant (This is version 3.9.0 at the time of writing)
  • A text editor.

Initial Setup

First, we need to make the mod folder so that Skin Deep can load our custom map, any custom textures or script behaviours we need, and so that you can distribute it to other players.

  1. Go to your Skin Deep installation directory. It’s usually in <Steam Installation Folder or SteamLibrary folder>/steamapps/common/Skin Deep/ if you have installed the game from Steam. You should see a folder named base in there; that’s the base game, and you can look at all the scripts and assets there for inspiration!
  2. Create a new folder next to base named whatever you like; it should probably not have any spaces in the name though. This is your mod folder!
  3. Inside this folder, create two empty files: description.txt and pak000.pk4.
  4. Write a single line of text in description.txt; this is how your mod will display in the ingame mod browser.

Setting Up DarkRadiant

Now we need to set up DarkRadiant so that we can make a map for Skin Deep.

  1. Open DarkRadiant. If it’s your first time using it, you will be prompted to configure the Game Setup.
  2. Choose “Doom 3” as the Game Type, and use Skin Deep’s installation directory.
  3. Enter base in the “Mod Base” field.
  4. Enter your mod folder’s name in the “Mod” field. This will let DarkRadiant read your map, but also reference all the existing sounds, models, textures, etc from the base game.
  5. You can perform the above steps using DarkRadiant’s Game Setup menu option if you already use it - but you already know that, if you do!

Creating your first map

  1. Create a new map in DarkRadiant, and save it inside mod-folder/maps/. Name it whatever you like; in this guide, let’s name it first_level.map.
  2. Then, create a new empty file next to it in mod-folder/maps/ called first_level.script.
  3. Write the following in that file:
    namespace first_level
    {
     void main()
     {
         sys.println("loaded map script first_level!");
     }
    }
    
  4. This file will be used by your map to perform any kind of custom logic, like tracking objectives or performing special scripted actions. You can read some of the base game map scripts for examples of the kinds of things they do!
  5. Make a simple box room in DarkRadiant. Exactly how to do this is a little outside the scope of this guide, which will focus on Skin Deep specific setups, but the DarkRadiant User Guide and the DarkMod wiki’s Video Tutorials Page do a great job of how to use the editor to make maps for the community-maintained Thief-like game “The Dark Mod”. If you are familiar with Hammer or TrenchBroom, then a lot of those skills will transfer over to DarkRadiant - I only started using it a few days ago myself!
  6. Edit the worldspawn properties for your map, adding/setting the following keys and values:
    call: first_level::main
    cryo_briefing: Welcome to my cool level!
    shipname: First Level
    shipdesc: Simple Box Freighter
    
  7. Create an info_location entity somewhere in the room. This will tell the game that this room is a specific place on the ship; a lot of systems depend on locations existing, such as cats being able to tell you where the nearest key is.
  8. Set the location value on the info_location entity to name your room. In this tutorial, we’ll call it Main Room.
  9. And just so it’s not dark in here, add a light to the room. We’ll discuss destructible lights later.

Spawning Nina and Testing

A level is nothing without Nina Pasadena, our favourite deep-freeze insurance agent! Let’s get her out of cryosleep and into action.

  1. Create a second box room away from the first; this room will hold Nina’s cryo pod interior, so it needs to be unlit and inaccessible to the player during normal gameplay.
  2. In this room, add an env_cryointerior entity. This is where the player will spawn, and the game will automatically set up lights and interactivity for it.
  3. Back in the first box room, create an env_cryospawn and place it on one of the walls, so that its back side is flush with the wall. This is where Nina will pop out at the start, and where the player can peek into the level to get a sense of what to do when they emerge.
  4. Set the env_cryospawn’s target0 property to point at the env_cryointerior you made. Its name by default will be env_cryointerior_1.
  5. Now you’re ready to test your map!
  6. Open Skin Deep, and load your mod from the mods button on the main menu.
  7. Then, open the developer console with Shift+Esc, the ` key on a UK keyboard, or the ~ key on a US keyboard.
  8. Type dmap first_level and hit enter. The dmap command builds lighting and collision for your map - you will need to run it every time you make changes to your map.
  9. You should spawn into the cryo pod and be able to enter the level! Hooray!

Adding Stuff

It’s a little boring to walk around an empty room with no items, cats, pirates, or anything to do. So let’s change that, shall we?

Cats and Keys

Get meowt of here!

  1. You can add a cat to rescue by creating an env_catcage or env_catcage_b entity. They both work identically, but they have different voice acting.
  2. Set the name_cat property on the env_catcage to give this cat a name! In this tutorial, we’ll call them Sparkles.
  3. Don’t forget to add an item_cat_key to the level… otherwise, that cat’s never getting out!
  4. The game will automatically track how many cats are in the map, and the player’s progress.

Necessary Machinery

Many of the ship’s machines provide vital gameplay functionality for players. Let’s get the basics set up!

  1. You’ll need a health station when you slip on a banana peel, get shot, step in glass, get exploded, and so on. Add one by creating an env_healthstation entity, and place it so that the little handle is facing outwards from the wall.
  2. It’s really, really easy to lose items - every map needs a Lost and Found. Also, pirate skulls will complain in the debug console if one doesn’t exist, so it might even be necessary to get them to work right. Create one with an env_lostandfound entity.
  3. A healthy ship requires frequent repairs - if you damage a vital system in a pirate-jockeying-related accident, you’ll need a way to get it working again. Create a func_repairhatch entity and place it on the ceiling. Then, create an aas32_flood_flybot entity and place it in the air; this will generate pathfinding for flying objects like swordfish and repair bots.
  4. A ship isn’t a ship without an FTL drive. No, really, a bunch of stuff does not work unless you have an FTL drive - this is a holdover from earlier versions of the game. You can place an env_ftl_mini outside the ship somewhere to satisfy the requirement if you don’t want a big internal one.
  5. Players will be lost without an in-game map screen, and need a way to save the game. Details on setting up the visual component of the in-game map screen will be given later in this guide, but for now, place an env_infostation on a wall. It will automatically track various kinds of objects for you once the visuals are set up, and you can now save the game and request repairs.

Deadly Weaponry

The tools of the trade.

  1. Many of the game’s useful items are named moveable_*. For example, Soap is moveable_soap. Place some around and the player can pick them up and hurl them around. Same goes for bananas and pepper.
  2. Loadable weapons like the shotgun (moveable_item_shotgun) can be given initial starting ammo. For the shotgun, use the inv_ammo_shells value to set how many shells it starts with already loaded.

Pilfering Pirates

Let’s get the Numb Bunch involved.

  1. Create an aas_flood_48 object on the floor. This will produce navigational data for the enemies to use to navigate your map.
  2. Create a monster_thug entity; this is your first pirate! Name them with their displayname attribute. We’ll call ours Stinky.
  3. You can attach some (but not all) objects to a pirate’s belt with the def_beltattach1, def_beltattach2, etc variables. Try attaching a cat key by setting def_beltattach2 to item_cat_key!
  4. Create an info_enemyspawnpoint on the floor somewhere. This will be used by your pirates skulls to respawn; they will automatically pathfind to it.
  5. You can also set up patrols for enemies by making their target0 point to certain path entities, but I haven’t fully experimented with this yet. Without a path target, they will patrol randomly in rooms and occasionally go through doors.

Doors

One room is simply not enough. It’s getting too busy in here with all this stuff! Let’s make some more rooms, and some doors between them.

  1. Create a second room joining the first; cut a hole in the wall so you can walk between them. Make sure not to leave any gaps that would cause leaks. Also create a second info_location and name it!
  2. Add a func_static entity, and set its model variable to models/objects/doorframes/frame_b.ase. Set its gui variable to guis/game/doorframe_infopanel.gui. Place this in the doorway between the rooms.
  3. Create a func_door_single entity and place it in the empty area that the func_static provides; it should fit like a glove.
  4. Set the autodir value to 1 on the func_door_single. That will allow it to open and close correctly.
  5. Set the autobutton value to 1 if it is not already set. This will let the door spawn buttons on the frame!
  6. Now, here’s a slightly tricky part. Create a brush the that slightly overlaps the doorframe’s size, and make it 1 unit thick so that it can fit inside the door’s model. Give it a nodraw texture on all sides, but then set a single side facing out from the door to the visportal texture. This is a visportal, and creating these will be very useful for a huge number of reasons.
  7. Place the visportal inside the door so that it completely seals the hole, and sits inside the door model, dividing one room from the other.
  8. Create an info_locationSeparator and place it so that it overlaps with the visportal. This will tell the game how to split the two locations up, and will help with things like vacuum breaches (we’ll get to setting up outer space later).
  9. Run the map with the dmap command and you should be able to walk between the two rooms!
  10. Add env_sign_marquee96 entities on either side at the top of the door frame to provide useful signage to the player. Set the gui_parm0 variable to set the text of the signs.

Vents

Why take the obvious route, when you can take the sneaky one? Let’s make a vent.

  1. Create a third room. Position it some distance from the first two, so that there’s room to crawl between the rooms. Don’t forget its info_location!
  2. Make a tunnel between the third room and whichever room you want to connect it to; it should be 64 units wide, and 32 units tall. If you have corners in your tunnel, make them a little bigger than 64x64x32, to help clue the player in they can turn around, if you like.
  3. Add a func_static entity, and set its model variable to models/objects/grate/grate_hole_64x32.ase. This is similar to the door frame model, and gives you a nice frame in which to position the vent itself. Put it just inside the tunnel holes, so that it is flat on the outside and has no lip sticking out.
  4. You can also set its noclipmodel value to 1; this will prevent the player bumping into it during gameplay, but may allow things like bullets in and out. If you do not set this to 1, you will need to make a tiny clip brush with a slope that lets the player crawl over the lip of the model.
  5. Create a func_ventdoor_inter_64x32. This behaves much like the door. Set its movedir value to -1. Place it within the vent frame. Place it so that the handle on the texture faces the room and not the vent itself.
  6. Set gui_parm0 to choose which text to display on the outside, and gui_parm1 for the text on the inside.
  7. Create a thin visportal, like you did for the door, and place it so that it intersects the vent door. You do not need a locationSeparator for vents.
  8. Make another vent door with a frame and a visportal at the other end of the tunnel for the other room. Boom! Vent time!
  9. You should now be able to get into and out of the vent when you compile and run your map! But it’s not dusty inside the vent, and you can turn around easily. To solve this, you need to add a brush inside the vent for the length of the tunnel, a little smaller than the vent itself, and assign it to be a func_confinedtunnel. Set its texture to the confined texture.
  10. Set the baseangle value on the func_confinedtunnel to select which direction to confine the player in. This will usually be either 90 or 0.
  11. Set its confined value to 1. This will restrict the players movement, shadow them, and make their sneeze meter build as long as they remain inside the trigger volume.
  12. If you have corners in your tunnel, or larger spaces that you would like to remain dusty but not constrict the player’s movement, create new func_confinedtunnel brushes and set their confined value to 0. They will keep the player shadowed and keep building the sneeze meter, but won’t stop them from turning.
  13. These volumes will also enable vent purges if a pirate sees the player enter a vent.

Vent Barricades, Keypads, Notes

It’s too easy to get between rooms. Let’s add a simple keypad puzzle.

  1. Edit the func_ventdoor_inter_64x32’s variables and set barricaded to 1. This will make the vent locked by a barricade when the level starts.
  2. Add an env_maintpanel_vent on a wall somewhere. When the player interacts with this, it will unlock the vent! Compile and play the map to see it for yourself.
  3. Seems a bit easy… let’s lock the maintenance panel. Add a func_keypad entity near the panel, and set its target0 variable to point to the panel. The panel’s name, by default, will be env_maintpanel_vent_1.
  4. Then, set the func_keypad’s code variable to a four-letter word, like bees.
  5. If you play the level now, you have to type in BEES to unlock the panel and then unlock the vent!
  6. So far so good - but the player won’t know that, so let’s add a note to the wall.
  7. Create an env_note_paperwall entity and put it on a wall somewhere. Write some useful text in the gui_parm0 variable, something like:
    Hey, numbskulls!\n
    Don't forget the password: 'BEES'!
    
  8. Take care not to use double-quote marks, as they will terminate the text early in-game.
  9. Great! But the code is the same every time, and in the base game, it isn’t. So what’s up with that?
  10. Open the worldspawn properties, and set the variable keypad_random to 1. This will randomize keypad codes every time the level is played!
  11. But now our note doesn’t make sense any more. BEES will not be the password! To fix this, set the note’s target0 property to point at the func_keypad (by default, it will be func_keypad_1).
  12. Edit the gui_parm0 text, and replace the password with the magic string %s%s%s%s, so it’ll look like this:
    Hey, numbskulls!\n
    Don't forget the password: '%s%s%s%s'!
    
  13. And boom. Now you’re cooking!
  14. Note, the string subtitution doesn’t work unless keypad_random is on. So if you use fixed passwords, you’ll have to write the notes manually.
  15. Forget-me-nots work the same way, they just use a string to display the code. If you make one, just set its gui_parm0 to %s%s%s%s.
  16. It’s also possible to mangle the order of the displayed code, like showing it backwards; set the codeindexes value on a note to any order other than 1234 (e.g, backwards would be 4321) and it’ll automatically rearrange the code when displayed. Good for sneaky pirate op-sec..!

Airlocks and Outer Space

Whew, it’s getting kinda cramped in here. Let’s get some fresh air… wait!!!

  1. There is currently nowhere for Outer Space to exist. Make a large box around the entire map, and set its texture to one of the sky textures, like textures/skies/sky_pinksky. We can set up a more complex skybox using an info_portalsky and the skyportal texture, but that’s not necessary right now.
  2. Now that the ship is safely inside and the map won’t leak, punch open a hole in one of the rooms.
  3. If you play the game now, you’ll notice that, uh, you can just… walk outside, and there’s air out there, and you can fall down into nothing with gravity that should not be there.
  4. Let’s fix this. First, create an info_vacuum entity outside the ship somewhere. This tells the game that this location has no air.
  5. Next, create an info_location out here in space, and set the location value to exactly #str_00000. This specific localisation key is required to tell the game that this particular vacuum location is actually outer space itself, and not an interior location in the ship with no air; if you don’t have this location set up correctly, Skullsavers will not correctly disappear when thrown out into space, and the lost and found will not work either.
  6. Make the hole in the ship big enough to fit an env_airlock object; make sure to rotate it so that the external door faces outside, and not inside the ship.
  7. Seal the edges of the airlock with walls; this may mean you need to make your room a little longer, or otherwise you can have a kind of tube that the airlock sits inside.
  8. Place thin visportals inside each of the two door areas of the airlock to seal the entrances to it.
  9. Now when you run the game, you should be able to open the airlock and exit to space correctly! You can fly around, and dunk that pirate’s skull out here!

Windows

Peering out into the void…

  1. Windows are a little complex compared to airlocks. Create a window-sized hole in one of your rooms, out to space.
  2. First, create a patch brush in DarkRadiant. This workflow is a little strange compared to the usual brushes, so you may need to consult the manual a bit. I certainly had to! This will make a one-sided rectangle brush.
  3. Use the Patch Thicken option; depending on the axis that the window will be placed, select thicken on X, or Y. Set the thicken value to 0, and it should make the patch be a two-sided plane; it will have no depth, but will be solid on two of its sides.
  4. Place this patch so that it completely fills the window area. Give it one of the glass textures; I recommend glass1_spacewindow_filter_subtle so that it’s easy to see through.
  5. Set this patch to be a func_fracture entity.
  6. Next, create an info_vacuumSeparator entity, and position it so that it overlaps the func_fracture.
  7. Create a visportal, and make sure it wraps around the func_fracture and overlaps the info_vacuumSeparator.
  8. Create a thicker brush that surrounds all of these other brushes. Set the texture to windowseal01_interior for the face facing into the room, and windowseal01_exterior for the face facing outside the ship.
  9. Set that brush to be a func_windowseal entity.
  10. Now the tricky part. Make the func_fracture’s target0 target the info_vacuumSeparator, and its target1 target the func_windowseal. Then, make the func_windowseal’s target0 target the info_vacuumSeparator, and its target1 target the func_fracture. Whew..!
  11. Now, when you play, you should be able to see out of the window and break it with something to vent the room to space. If everything is OK on your map, only that room should be vented to space, and other rooms (which you can visit with a vent or with the noclip command) should still function normally. You may need to adjust the position of the seal, visportal, and func_fracture to get things to render and behave correctly. I don’t know the exact details, but sometimes it doesn’t work quite right and you have to nudge things around.

Signal Lamps and Pirate Invasion

So, we can rescue cats and dunk pirates. But how do we finish the mission? Let’s get the level’s last setpiece working.

  1. Create an env_signalkit and put it somewhere in your map. Inside, outside, your choice! Usually the base game puts them inside near airlocks and outside near airlocks, windows, and trash chutes. If Outer Space is set up correctly, you should be able to summon a pod the usual way. But first…
  2. Create a closed-off space in your map, like we did way back for the cryopod, then put an env_catpod_interior inside it. This will automatically populate with weapons for the invasion phase, and will appear when you summon the pod with the signal lamp.
  3. Now for the pirates. Create a func_pirateship somewhere in your level; one of the corners in space is the best idea. It won’t be visible to the player when the game starts, so don’t worry if it would be seen from somewhere.
  4. Create a NURBS curve in DarkRadiant. This is a special spline object; you can fill in an arbitrary number of control points in its curve_Nurbs field. Create a path from the pirate ship’s spawn location to a point about 128 units outside your airlock.
  5. Set the curve to be a func_splinemover entity.
  6. Create yet another locked-off, unlit area in your map. Make this one far away from the ship - outside of the skybox, even. This is where you’ll place the pirates who come aboard the ship in the invasion; you can use monster_engineer for tougher pirates that use swordfish, for example. Put a pirate in here, and set their hide variable to 1.
  7. Set target0 on the func_pirateship to point to your func_splinemover NURBS curve.
  8. Set target1 on the func_pirateship to point to the reinforcement pirate inside the dark box.
  9. Set target2 on the func_pirateship to point to the airlock.
  10. If all went well, when you summon the catpod and rescue the cats, the pirate ship will spawn, travel along the spline curve, and then dock at the airlock. Once docked, it will move the pirate from the locked-off area to the interior of the ship, and they will move through the airlock.
  11. And that’s it! Your level is done, basically! You can now get the glorious “NINAAAAA!” chant after disposing of the invading pirates! Just one last thing…

The Interactive Map

Information superhighway…

  1. To get the infoscreen map and the player’s wrist-computer map to work, there’s a little setup that needs to be done for your map.
  2. First add two target_null entities in your map called loc_topleft and loc_bottomright.
  3. Position them so that they form the top left and bottom right corners of a square that covers the boundary of your ship’s floorplan (this is easiest to do from the top view in DarkRadiant). It must be a square, not a rectangle! The game will tell you in the console if the sides aren’t the same length.
  4. Add two properties to your worldspawn called map_topleft and map_bottomright, and give them the values loc_topleft and loc_bottomright respectively.
  5. Add a .tga format texture for your map display to guis/assets/mapguis inside your mod folder. The top-left and bottom-right pixels will be relative to your top-left and bottom-right location markers. Look at the base game maps for examples.
  6. Create a materials/mapgui.mtr file inside your mod folder. Using the base game’s file as an example, add a new entry for your new map, referencing the new texture you just made.
  7. In the worldspawn, add a new property called mtr_image and make the value the name of the entry in mapgui.mtr you just made.
  8. Close and re-open Skin Deep if it’s running, to refresh the image cache when you load your mod.

Loading your map from the Menu

It’d kinda suck if players had to open the devconsole to play your map, right?

  1. Add a new file to your mod folder: def/maps.def.
  2. Write the following in it:
    mapDef first_level 
    {
     "name" "first_level"
     "description" "First Level"
     "singleplayer" "1"
     "levelindex" "1"
     "internalname" "first_level"
     "levelimage" ""
        
     "size0" "805702930"
     "size1" "805702930"
     "size2" "805702930"
     "size3" "805702930"
    }
    

Note: the size* values here relate to the estimated time the loading screen should take, to help inform the progress bar. There is a warning against altering this value in the pre-existing test_scout map by level designer and Skin Deep developer Suzanne Will, so I definitely won’t touch it!

All Done!

And that’s as far as I’ve learned! There’s so much more to find out - security cameras, turrets, pirate loadouts, window barricades, door keycards… get out there and make something awesome! You can share your map by zipping up your mod folder and giving out to people. At the time of writing, I haven’t yet found the workshop upload tools, but they’re probably not too hard to figure out somewhere.

Thank you for reading this guide. If it helped you, please give this guide a mention when you upload a cool map for other people to play! Adios!

react to this post:
shocked x0 c'est la vie x0 yeah!! x0 huh??? x0 funny face. x0 sing!! x0 relaxing x0

© 2024. all rights reserved.