"A real man... oughta be a little stupid." - Ryuji Goda.

Report RSS Modding thoughts. Procedural Map Generation. My new limit-circumventing journey in Sourcemodding.

Posted by on

Hello everyone. So after months since release of Lost and Damned I started to dream of making another cool mod project in Source Engine.

For this time I decided that I'm not gonna base my next project on concepts from some already existing game (like the 2 previous projects of mine). Therefore, I'll have to make something with its own unique meaning and personality. Even though, nothing in our industry is purely original, every creator was at least inspired at some point from someone else's work at least once. Even my first project was done under big influence from immersive-sims and stealth games.

dhdsl

A Wider Picture.

Few days ago I realized one "small" point, which actually made me understand some of the criticism I've been receiving before. When it comes to making mods, my mind is usually fixated on the whole HL2 modding industry(kinda hyperbolized way of calling it, but you get the idea).

My decision-making process usually works in a way that if something wasn't present in most HL2 mods before, then it's gonna be super cool and original to add it in my mod and I often considered that to be nearly "revolutionary". The fixed-camera survival horror genre, the hotline-miami stuff, even stealth mechanics - they all are impressive, indeed, but only in a technical sense - the "this guy made x *but* in source, that's kinda cool yea". If I look at it from a perspective of an ordinary person, who's unfamiliar with all my modding insanity and just wants to try some cool game, it wouldn't make much sense to bother installing sdk2013, when i can get the actual game that mod was inspired from.


Although, in defense for my parody projects, they served as a great practice and challenge for my abilities. After the release of LaD, I promised myself that I'm gonna use VScript excessively in my next project to make cool stuff with it and make my modding process less exhausting and monotonous.

I'm a maplogic wizard, not an environmental artist!

You know what's my personal most exhausting activity in modding? The level design.

Man I hate having to spend whole months on just making an acceptably looking game environments. I consider myself an amateur mapper, my levels might be detailed well, but I think that geometrically they kinda suck. There's almost no verticality in my maps and they look pretty claustrophobic in some parts. Therefore, I can't consider mapping as one of my strengths, furthermore, it takes ALOT of time.

Seriously, it took me like 3 months to make some almost-finished city map and 50 streams on my YT channel are the evidence of that. Yeah the finished map would have lots of potential, but considering the fact that I'd have to make like a dozen more maps for FF:R? That's insane. No shit I'd get burned out every week and start questioning the worthiness of my work. Of course, that's just me, yo boy sellface was never about hl2 mappacks from the start, innit? My desire of making cool mechanics and stuff just by using maplogic or scripting has been my primary motivator for making mods. When I see someone else doing crazy I/O contraptions in their maps, I feel like I must prove that I can make even cooler stuff!

hammerplusplus zoHfNQyQwb

The concept of procedurally generated maps in Source.

The concept of procedural (or random) generation on its own is nowhere near being anything new. Despite that, I still believe there are many reasons why it would be a great idea for my next project.

First reason being the great replayability and mod length. Most of my projects are around 2-3 hours in length, and that's okay for a solo project. But there's no replayability in an ordinary mod. The reason why I could replay UnderHell for example was that it's a very long mod(more than 10 hours) and I simply couldn't memorize everything on my first playthrough, and because it's good and actually does have variety in some sections.

Second reason being obvious absense of the need to spend weeks crapping out decals, crates and boxes on each and every square meter. There are already dozens of good looking hl2 mappacks and it's definitely not my job to make another one. I'd rather spend that time making vscripts and troubleshooting my silly miscalculations in code.

And here began my journey to overcoming the need for static map geometry.

I started my new project under a working name "Sourcerama"(I'll make sure to keep it as WORKING name and eventually get some actual title, or I'll beat myself to death(can't have another gta4 dlc)). An RPG with roguelike elements and probably more. MapBase is included used as usual.

Firstly I made a little empty hollow-box map and included some templates for func_wall floor tiles and walls, then started making script which would generate a randomly sized rectangular room with a doorway and randomly selected wall textures.

The room generation basically worked pretty simple. I made a 128x128x16 floor brush template and 4 templates with walls each being on different side. So I can explain generation as placing floors/ceiling tiles and walls where needed on an imaginary grid, each tile being 128x128 units.

hammerplusplus WTV5dhKbiC

These tiles are 64x64, but i doubled the size after first tests


As I mentioned before, I used brush entities at that time, which brings us several problems, some of which are more difficult than the other ones.

1. Lack of npc body collisions
This is solved almost completely by forcing serverside ragdolls. I already have an example project where only serverside ragdolls existed and it didn't affect perfomance at all, so why not.

2. Lighting and decals
Since most of the map's lighting is baked and static, common light entities are no use for me. The only working solutions for dynamically generated lights are light_dynamic and projected textures.
Light_dynamics can't work on brush entities at all, because every floor or wall tile is a copied instance of the very first tile i placed on the map. So all the information about decals, like bulletholes or blood and the lighting is copied everywhere, which is unacceptable.

test0038

I tried using projected textures as lights and they mostly do good, but they're a good choice only for spot lights, like projectors, but they're impossible to configure to work as normal point lights, since maximum FOV for the flashlight is 179, higher values just make it off. I tried placing 2 projected textures at same spot facing opposite directions, but the lighting still looked very unnatural. Another weird aspect is that whenever I lit the room with a high-FOVed proj. texture, player camera has some strange self-illumination effect - getting closer to walls makes them appear brighter than when standing few meters away from them.

In the first video showcase I managed to light the room by using projected textures, but they were using relatively low FOV values and the one above was placed high enough above the whole room to cover it. The end results for randomized room were good enough to continue moving forward!

Generating Map Layouts.

I made a new map, this time with a big hollow box, around 6000 units in all directions, enough to fit a good set of rooms connected to each other with corridors. I took the decision of adding a basic pseudo-random number generator based on Linear Congruential Generator, so that all the randomness is decided by a single seed value. Which would not only make layouts be possible to replay, but it's also almost essential for debugging purposes.

The room placement algorithm was tweaked a bit. I made it so that every spawned room is assigned to its own ID and gets a random "type" value(which changes the textures on walls) and every new room spawns randomly not too far from the previous one, also added check for room overlapping, so that they wouldn't get conjoined by mistake.

test0022


As for corridors. I sticked with simply making them either straight lines between rooms or L-shaped in cases when rooms don't have tiles that share same x or y coordinates. In the second showcase I demonstrate the room placement and corridor planning.

By the way, you may ask how does the algorithm decide which rooms connect with each other? After all necessary rooms are generated, the algorithm makes an empty array of 'connections' where (index of each array's variable+1) equals the first room and the value of that variable equals the second room. So I randomly fill this array, checking for whether the corridor can be generated without intersecting other rooms, whether rooms are close enough and don't have too many entrances.

After the 'connections' array is complete, the script begins generating (also random) corridors between all rooms. The layouts turn out pretty interesting and maze-like(or call them backrooms if you dare). The third showcase video demonstrates this.

test0031


Edicts aka Engine's Entity limit.

Of course, there's no way I could spawn too many rooms without crashing the game. Source engine's limit for networked entities is 2048. Each entity takes its "slot" in entity dictionary, they're usually called edicts. If the limit is overfilled, the game crashes and there's no way to increase the limit without modifying engine.dll.


At first I could generate about 20-25 rooms and connect them with hallways without having a crash. Having a little more rooms would cause the game to crash during corridor generation. But the problem was that the edicts number wouldn't even hit 1600-1700 before engine kicks the bucket, which is strange and very far from the actual limit. And no, corridors wouldn't take THAT many edicts at once, but somehow the game still crashed.

Turned out the issue was that I spawned too many tiles at once. I usually generated rooms one by one on a key press, but all corridors were coded to be placed in an instant. So perhaps either TraceLines that I essentially use during generation take edicts for a very short period before disappearing(and freeing the edict), or it's impossible to spawn too many brushes on different places at once. I made a hacky way to delay the placement of floor tiles, so that every brush is placed with a random delay from 0.00 to 0.06 seconds.

I also divided hallway generation process into steps. Currently it generates one hallway at a time and after the last one is generated it also takes its time to place all necessary wall tiles on each side(demonstrated in the last video). So this actually solved my issue with engine crashing too early, nice! I still have to keep the entity count low, so that there's enough space for all the other entities.

Failed attempt at optimizing the entity count.

If we take a simple 2x2 enclosed room, with individual brush entities for floors, walls and ceilings, that would make 16 entities in total, and that's just for a tiny empty room! I definitely had to figure a way to lower that count.

So I came up with an idea of replacing the repetitive sets of brushes with a single big one. Swapping every combination of 2x2 square of 4 brushes with a single floor/ceiling tile twice bigger than normal one. And so on..

The idea should've worked just fine and it would lower the entity count on the fly! But have I mentioned that I used point_templates and that my algorithm used EntFire command for spawning any tile? That's the problem. Ent_fire is executed independently from the script, so it always has a small delay.
Which completely fucked up any of my attempts at finding repeating brushes and replacing them, without touching already spawned replacements, I got tired of it and decided, that point was requiring me to throw the brushes and templates away and use models for tiles instead.

Mesh-based mapping is the way to go.

I used Propper to convert my brushes into props for the first time and holy cow, that tool is so convenient, it literally makes a model file out of specific brush entities from your Hammer Editor's VMF file.
I changed the whole placement algorithm to work with models, so I don't even need to keep any template entities in the map file as now the script spawns them directly.

Placing DLights

After transitioning to models, I have unlocked light_dynamic for the usage. It works pretty well for my layouts and despite having a limit of 32 being active at once, turns out I don't even have to make algorithm for disabling unneeded lights, since the game already does that for me automatically(briefly shown in the last video)


There's only ONE major issue with them that is unnerving. They never cast shadows and therefore they lit up everything around them, even floor tiles that are behind a wall.

Dlights can be configured to light up only a certain amount of tiles around, but obviously I can't just configure them in a way so that they light all tiles in their room, but never touch tiles outside of that room, unless they're corridor tiles at some entrance of the room.

Currently I have thought one possible solution for this problem, and that is marking all floor tiles in the lit room and so whenever player has line of sight with one of the floor tiles, the light turns on, otherwise it would turn off.

Placing Ents and The Finale

I made a simple system for randomly placing stuff on each tile, and for demonstrational purposes I decided to make script generate rooms with some basic random props(crates and barrels), zombie enemies and ammo.

And that's it! I made the last vid showing off generation of 20 connected rooms and some bits of gameplay.
On next day I changed all walls to be 16 units thick, because 8 units were not enough to obstruct NPCs line of sight and they'd see the player through all walls.

Another issue is npc navigation. I couldn't use nodes, because placing hundreds of them on a flat grid would cause the game to crash itself after one zombie takes a little too much time deciding which node it should try going to. And I also tried placing nodes in game, or regulating the connections between them, but both attempts failed. I think I'll just code a little ai behavior override so that upon losing LOS with player, npcs would walk to a tile that both them and the player see.

Final Words.

Throughout the making of this system I had to troubleshoot lots of silly issues and miscalculations for hours. Despite that, I still enjoyed the process of making it, because in the end I get this awesome thing.

Obviously, I'm nowhere near being finished with this, there's still lots of room for improvement. I need to make rooms have tweaked shapes to avoid everything being rectangles and make a whole system for putting details everywhere so I can stop having my system get compared to liminal spaces. Besides, I want to fill levels with lots of gameplay stuff, idk, puzzles, different enemies, traps, pretty much anything that will come to my mind.
Until next time.

Post comment Comments
richyricky
richyricky - - 13 comments

Nice idea! Maybe you could use static lights for the geometry and then use dynamic lights that only affect the players' viewmodel and zombies that may you get the best of both worlds.

Reply Good karma Bad karma+2 votes
SellFace AuthorOnline
SellFace - - 103 comments

Unfortunately I can't see how static lights would work along with random generation. They can't be spawned on the fly or change settings. Plus, I still use prop_dynamics for geometry, which can't cast any shadows except for cases when env_projected texture is used.
I have thoughts regarding the light bleeding through walls issue that I might just do a system with multiple raycast checks from inside of the lit room to player's camera and if player sees atleast one of them, then turn on the lights, or else turn them off.
In video showcase there are completely no doorways between rooms, but I actually plan making almost each entrance to one room or another become either a doorway, vent or just some arc-shaped structure, thus making the (can the player see the light inside the room so we can turn it on seamlessly) detection easier.

Reply Good karma+1 vote
NiiRubra
NiiRubra - - 472 comments

It is pretty amazing how you managed to make the ol' janky Source-engine comply with this cool idea you have.

Reply Good karma Bad karma+2 votes
Musie_(MyCbEH)
Musie_(MyCbEH) - - 232 comments

No one created such experimental solutions on source engine modding

Reply Good karma Bad karma+2 votes
SellFace AuthorOnline
SellFace - - 103 comments

Update on the dynamic light issue!
I spend whole day today implementing doorways between rooms and made a system which turns off all dynamic lights in a room when player is out of its perimeter and none of the TraceLines fired from room entrance points hit the player. So light is on only when player sees entrance to the room. This works almost seamlessly and I'm satisfied.
I also tested some layouts on 40 rooms. They took around 1300 edicts out of 2048.
If implement some entcount optimization, I can lower that amount greatly and free lots of space for room detalization and gameplay elements.
The slightly low fps on big layouts can also get fixed by disabling drawing of far tiles.

( you might even take a peek into actual dev footage if you dare: Cdn.discordapp.com )

Reply Good karma+1 vote
Post a comment

Your comment will be anonymous unless you join the community. Or sign in with your social account: