Journals

darkphoenix_6812 years ago2011-08-07 21:44:44 UTC 7 comments
Well, it's certainly been a while since I was here last. I haven't built any more HLDM maps in the last couple of years - way too much other stuff on my plate - but I'm starting to think about getting back into it...

Maybe... :-)

Of course, all that other stuff is still on my plate, so thinking may never turn into doing...
darkphoenix_6815 years ago2009-04-13 19:14:41 UTC 4 comments
I've been promising it for a while now ... and here it is! I've just released my ninth HLDM level: Canyon: Redux.

I'd love to hear what you all think! :-)

Edit:
On a related topic, when uploading I notice it says "Upload your screenshot. You can add extra screenshots later." How exactly would I go about adding additional screenshots? I can't seem to find it... :-)

------Journal combinatorial services by TheGrimReafer-------

In the comments to my last journal post, I got asked why I bother writing such extensive documentation for "levels nobody gonna play more then 1 hour max?" I replied there, but felt my reply deserved a post of its own. So, uh, here 'tis:

Uh ... why?

For one thing it's DM, not SP, so I sorta hope people will play it more than once. ;-)

I write big documentation because every level I build - every HLDM level - has its own story. It's part of the process. Maybe people want to read the story, and maybe they don't. I write big documentation because it's what I do. When I'm not a level builder, I'm a writer. (Well, gamer, and reader, and general-time-waster, and watcher-of-DVDs, and writer. It's in there somewhere...)

Much of the time involved in FINISHING the documentation - 'cos, y'know, most of it is worthless waffle that nobody will read anyway - is getting the credits right, giving due and proper acknowledgement of all the textures I've used which were made by other people. Getting THAT right is important -- anything less would make me a lowly scumsucker who passed off other people's work as his own!

And sure, I'm sure there are people who think my writing four novels was a waste of time too.

shrug

As for why I continue to build levels for goldsource at all ... well, didn't think that would need to be explained on THIS site. But I build 'em for myself and my friends to play, and HL is one of the few games we can run without difficulty at work where we most often play. That's all. I choose to make them available here to give back something to the community from which I learned a trick or two over the years ... but that's a bonus. Certainly the half-dozen or so comments I get on each level are nice - the positive, and even the negative - and they teach me something too, but they hardly repay the months (even, in some cases, years) of work I put into each level. No, I build them for private use!

Since I'm also working on tweaking the SDK, this may well be the last level I release that will work (almost) properly without my PetesDM mod. OTOH, since I've pushed the engine about as far as it can be pushed, maybe it IS time to branch out, to find something else we can put on the machines at work. shrug

Edit:
For those who are interested, I've got two novels completed and two awaiting completion. They're self-published (partly because of legal reasons - by some definitions they're fanfic - and partly because I wrote them for much the same reasons I make my levels...) but that doesn't mean they're bad. Admittedly, I'm a little biassed, but I think most people here who know my mapping practices know that I wouldn't make it public if I didn't like the quality... :-)

The two completed ones are available as PDFs at http://www.petesplace.id.au/array_wars/ (and at LULU, but not yet publicly searchable; if you're really keen let me know and I'll give you the link!) They're, basically, Star-Wars/Matrix parody crossover novels.
darkphoenix_6815 years ago2009-04-12 00:03:10 UTC 3 comments
Almost there...

sigh

I have finished the documentation for my new Canyon: Redux level -- it took a bit of work to get the acknowledgements section right, to list all the people who created the textures I used, but it's finally done!

Meanwhile, I have been running the final compile of the level. For consistency, there were just a few minor details which needed tweaking, so I did that last night and started the compile. On my computer at work, with its 3Gb of RAM, the VIS portion of the full compile takes around 14 hours. On my laptop, with its meagre 1Gb - well, let's just say it probably won't be finished today. Neither of my desktop machines has more RAM than that either - although possibly it would compile a little faster on one of those...? Definitely time for a hardware upgrade, methinks!

If all else fails, I'll run the compile overnight at work -- but I won't be back there till Wednesday afternoon at the earliest!

Sometime in the next week, I'll be releasing the darn thing. Until then, I've got screenshots up on my Mapping Page if anybody wants to take a sneak peek...
darkphoenix_6815 years ago2009-04-06 23:40:35 UTC 2 comments
One should always playtest a level for a week or two before releasing it into the wild, don't you think?

In fact, if I hadn't taken so long to write up the documentation for my latest level (still unfinished, by the way - the documentation, that is) I would have probably released it ten days ago. Fortunately I didn't, because in the course of playing it I've found a few textures which need fixing - and one particular escape route which needed a redesign...

The new plan is that Canyon: Redux will be released sometime over the Easter weekend... :-)
darkphoenix_6815 years ago2009-03-13 02:08:48 UTC 2 comments
I done been away for a little while. Away from TWHL, anyway. I'm still tweaking Canyon_Redux (it's a sickness, I tell ya!) and it's probably a week or two away yet.

In other news, though, it seems that the links to my maps broke while I wasn't looking. I was using an ".htaccess" file to redirect all download requests through the download counting code on my website -- and I suspect my hosting provider has changed something which broke the mechanism.

I'll look into it further over the weekend -- but for now, my links should all be working again!

Sorry for the inconvenience! ;-)
darkphoenix_6815 years ago2009-02-23 10:04:30 UTC 3 comments
I've finished Canyon: Redux! (Less than a month! That's gotta be some kinda record for me!)

We'll spend a week or two play-testing it to make sure I've caught all the glitches, and then I'll put it online...
darkphoenix_6815 years ago2009-02-20 03:58:07 UTC 5 comments
Ever had the need to change the rotation speed of a rotating func_train?

In my latest level, I built a fancy rotating door out of two func_train entities; one to serve as the sliding support, and one to actually rotate out of the way. I figured it should all work, because I knew that the path_corner definition in the FGD file included a "New Train rot. Speed" option.

Possibly I'm missing something, but it just didn't work. The door moved back and forth, but the rotational speed did not change (or, more to the point, the rotational direction did not change!) Eventually I hunted through the code until I found the relevant section, and it seems that the option is just not wired in.

When trying to fix this unfortunate omission, I ran into a couple of other peculiarites, the first being that the definition in the FGD file just did not seem correct. It called for yaw_speed(integer) where the prompt was clearly asking for three numeric values...

Ultimately, in the course of getting it to work, it turned out to be simpler to change the FGD entry. Sadly this means that if you wish to take this code and make use of it, you will need to recompile any existing levels which contain rotating func_trains. I say "sadly", but upon reflection it is possibly a good idea that this code not change the behaviour of any currently working levels...

So anyway, making this work is a very simple two-step process.

First, edit your FGD file. Find the path_corner entry, and replace the "yaw_speed" line with the following:

avelocity(string) : "New Train rot. Speed (y z x)" : "0 0 0"

Using "avelocity" means that no code needs to be written to get the vector value into the engine; it is handled by the initialisation code. (In fact I suspect that yaw_speed would be read, but handled as a single value rather than a vector, so the change is actually required!)

Second, open your dlls\plats.cpp file. Search for "CFuncTrain :: Next" and look through the function until you find the following:

if ( m_pevCurrentTarget && m_pevCurrentTarget->speed != 0 )
{// don't copy speed from target if it is 0 (uninitialized)
pev->speed = m_pevCurrentTarget->speed;
ALERT( at_aiconsole, "Train %s speed to %4.2f\n", STRING(pev->targetname), pev->speed );
}

Directly below that, add the following lines:

if ( m_pevCurrentTarget && m_pevCurrentTarget->avelocity != g_vecZero )
pev->avelocity = m_pevCurrentTarget->avelocity;

Be warned: While this does indeed work, I seem to have a logic problem in there somewhere (in the equality test to g_vecZero) in that it is applying the new angular velocity EVEN IF UNDEFINED, or if avelocity is set to (0,0,0). What this means is that any rotating func_trains you might have in existing levels will cease to rotate! If this is not a problem for you, then go ahead and use this code. Otherwise - well, turns out it breaks at least one of my other levels, so I'll be doing my best to fix it soon! Stay tuned! :)
darkphoenix_6815 years ago2009-01-20 20:47:15 UTC 2 comments
I originally posted this on LiveJournal, so it's a little light on technical talk, and a little heavy on explanation to non-Hammer-savvy civilians, but it's relevant here too! :-)

January is half-gone already, and it's been an interesting month so far, full of ups and downs. I'm not here to talk about those, though; I'm here to talk about my next HLDM mapping project. I'm actually slipping this in front of the ongoing work on Lab11: Erupt, and I'm basically treating it as a "quicky" project -- which, for me, means less than a month. Hopefully. Needless to say, there's a back-story...

Many moons ago (mid 2004) my buddy Ben built an HLDM level of his own. I'd already built a couple, and was working on another - off-hand I don't remember which one - and he wanted to get in on the act and express a few ideas of his own. It was his first -- and so far, his only; with a wife (and now a child) Ben does not have as much free time on his hands as I do -- and he'll be the first to admit that it is not as, uh, visually polished as it could be. That level was (and is) called Canyon.

The original design idea behind Canyon was the same original idea that drove the creation of my first level, LavaLab: quite simply, "glass walkways". In fact, one of my false starts before I settled on the LavaLab setting was a canyon. I didn't know enough back then (and was working under an artificially imposed constraint which I have since ignored) to do the idea justice, and finally set about to remedy the situation with my most recent level, Lab11: LavaLab... Of course, the remake has no glass walkways at all, and the original had them pared back to squeeze the level into that constraint I mentioned, so I haven't yet managed to produce a level making proper use of the idea. I'm sure it will manifest itself at some future point in my map-building career...

(Edit: Ben tells me that the original design idea behind Canyon was actually one of the extremely vertical, vertigo-inducing levels of one of the Jedi Knight shooters, combined with some other canyon-like level we'd seen. Live 'n learn, huh?!)

Canyon, meanwhile, ended up with a grand total of one glass walkway, and absolutely no "canyon"-like features apart from its being a long deep hole. The story that we developed to explain the purpose of this facility was that it is a garbage disposal thing, but there was little in the design to back up that explanation. A couple of years ago, with Ben's permission, I set out to "reimagine" the idea, making the place a rocky hole on an asteroid somewhere, with a garbage compactor at the bottom. I was using a set of grungy tech textures from the same guy who made the Egyptian texture set I'd used in my Necropolis: Mausoleum level, and I was excited. The attempt stalled, however - I'll get back to it sometime - and I then decided to work on the remake of LavaLab instead.

At around that time we discovered that Ben had mislaid the source files - the MAP and RMF - from which the playable BSP file is compiled. Without those, he was unable to make any changes to his level, and over the years that we've been playing it, he had numerous things he wanted to fix, or change, or add. At the time I hunted out a decompiler, which takes a BSP file and generates a MAP. The process is far from perfect, and the resulting MAP file would have required a lot of work just to get it back to its current state, so he (and I) gave up on the attempt.

Then, in the lead up to my release of Lab11: LavaLab - something we both highly anticipated (Ben with delight; myself with more than a hint of trepidation) - we stopped playing whichever of my levels we had been playing and switched back to Canyon. After we started playing my level, of course, we had Ben's on our minds. One thing led to another and, on a whim, I went looking for another decompiler. Of the few I found, one gave us a MAP file which was almost useable. I rubbed my chin thoughtfully, and then I told Ben I had a working canyon.map file. I offered him first dibs on jumping into it and fixing up all the things he wanted to fix.

When he declined, I made my counter-proposal: I would take it and work on it. He agreed.

For a time I simply sat and stared at it, in the editor. (Interesting typo there; I originally typed "deitor"!) Where to begin? It didn't take me long to decide that I would use his work to arrive at a hole of the same dimensions, and with the same doors and walkways and layouts -- but that I would rebuild the level from scratch, within those constraints, with my own design sensibilities. It was the only way I could get fully enthusiastic about the project! The original transformation of the layout from untextured blockwork to final design went fairly quickly: after all, I did not have to think (much) about layout and could focus all my energy on detail and texture. And since I already knew I'd be going back to the same texture set I'd wanted to use the first time I reimagined the level, that texture set began to drive detail design much as its sister set had guided the design of Mausoleum...

I very quickly arrived at something which had both Ben and myself really excited about what I am now calling Canyon: Redux... I also very quickly pushed the compile time up from minutes to hours. :-)

So far, Canyon: Redux has no glass walkways at all... Additionally, because 18 months of work on Lab11 had cured me of any wish to do rocky walls (to do them realistically, they rapidly consume the available resources) I have opted for the same full-tech approach of the original Canyon. I always like to try new things when building a level -- I'm more than happy with how my garbage disintegration field is shaping up, and the round doors have me quite excited, although I haven't yet proved I can get them to work. Revisiting complexities that I've already proved (to myself) that I can make work ... there's just no fun in that!

Which brings me to last night. After Ben and I finished firing it up -- 30 minutes in Lab11: HeatHaze (which is merely Lab11: LavaLab with gorgeous orange fog, and surprisingly the level works quite well for its size, even with only the two of us) -- we were talking about Canyon and Redux as we packed up our stuff in preparation for our respective trips home. (We play after work most nights...) Ben referred to it as "this thing we call Canyon", and the phrase lodged in my head. I decided we needed something to explain the actual "Canyon" name, given its complete un-canyon-ness, and the idea of having graffiti tagging the facility with its "common nickname" (given, no doubt, by those who worked there) occurred to me. (I'd already been thinking of using some generalised graffiti art somewhere, but that thought now crystallised into something very specific.)

Of course, while I'm fully confident in my mastery of Valve Hammer Editor when it comes to building levels, I have a dim view of my digital art abilities. So making a suitable piece of graffiti art for inclusion into Redux was something of a challenge.

I do like a challenge! I have to say, I'm pretty happy with the end result; it's certainly better than any of the textures I'd made up till now. Of course, it is applied over one of the wall textures I was already using so it would fit seamlessly into the level -- making a texture from scratch is still beyond my abilities...

Also last night, apart from putting the finishing touches to my disintegration field, I solved another problem which threatened to stall my progress. I had all my wonderfully circular doorways -- they existed in the original Canyon, I merely found a texture to suit them -- which had to open into much smaller (than the exterior space, anyway) square rooms. My solution was to construct a square-to-round transition piece. I'm still compiling last night's efforts so I don't know exactly how it will look in-game, but I'm pretty happy with it...

So there you have it. New project, coming soon... :-)
darkphoenix_6815 years ago2008-11-27 04:01:13 UTC 1 comment
I just tweaked my "Coding Fog Addendum" tutorial to incorporate the new code I've added...

The fog was, originally, switchable -- on or off. I've added "fade on" and "fade off" times, so that instead of just blinking in and out of existence, it actually fades in and out.

(I got it working so I could try out my "lab11_heathaze" idea, but at some point I shall have to design a level with fog in mind... ;-))

Edit: Of course, it wasn't until the drive home that it occurred to me that my revised version will probably do extremely bad things to network latency -- something I probably won't get to test until next Monday now...

sigh

Edit II: After all that, I've decided that Lab11: HeatHaze looks pretty damn good with fog, but the whole fading-in thing (which works fine) is not so great for the level. And yes, it does play havoc, so I'll need to revise the tutorial again sometime...
darkphoenix_6815 years ago2008-11-20 21:53:02 UTC 2 comments
Now that Lab11: LavaLab is finished, released, done and dusted, my thoughts are turning to what I should tackle next. The rest of this month, of course, is taken up by NaNoWriMo: I'm writing a novel about how a gaming geek with no social skills handles a zombie uprising -- it's called Half Life Zero: No Life... It occurs to me that my next NaNo attempt should possibly be the story of my Lab17 complex - or rather, the story of a lowly security guard who works there... :)

My very next level, of course, will be Lab11: Erupt, the post-disaster variant of Lab11: LavaLab. My original LavaLab: Erupt basically threw away everything outside of the lab itself -- and one of the criticisms aimed at it was that much of the freedom inherent in the original level was lost in the spinoff. This time, the Erupt variant will retain much of the freedom of the original while opening up new options. I hope. Of course, this means it won't be a 10-minute conversion! There's a lot of work that will go into the make-over, so don't hold your breath... ;-)

I am also tempted to release Lab11: HeatHaze, which will be the very first level of mine to require my petesdm mod. Since I put all the effort into getting working fog, I feel like I should make use of it. In theory all it requires (apart from distributing my mod) is to add an [ent]env_fog[/ent] entity and recompile. In practice, however, what I really want to do is have the fog (sorry, "heat haze") fade in as the level floods, then fade out as the lava recedes. Which means modifying the fog code to allow timed fading rather than instant on/off...

And after that? Who knows. So much to do, so little time. I have numerous ideas to tackle. Also, I build HLDM levels for GoldSource because we cannot use Steam at work (behind way too many firewalls) so I am somewhat locked in to that engine -- but I really do want to have a play with Source too. sigh
darkphoenix_6815 years ago2008-11-19 09:16:52 UTC 0 comments
For those of you who have been waiting so very, very patiently for the new level that I've been hinting at for so long: it's ready to go!

Check out Lab11: LavaLab from my maps page...

I hope it was worth the wait! (It has been a long time coming!) :-)

Edit: And what are the odds? I released this level to the world on Half-Life's 10th birthday! Great timing on my part -- I wish I'd known, I'd have put something in my level to acknowledge the date!
darkphoenix_6815 years ago2008-11-11 20:46:20 UTC 5 comments
Possibly you may have heard rumours (such as the one I posted in my journal three weeks ago) that my newest level, Lab11: LavaLab, was almost ready to be released to the world.

Well, that was true at the time. All I had to do was put together the README file to go with it, and it was ready to go. Of course, I found myself suddenly busy with other things, so the documentation (and hence the release) got delayed.

Which is, all things considered, a good thing!

We've been playing the level, on and off, for the last couple of weeks, and I had noticed a couple of extremely minor issues. None of them, by themselves, was enough to warrant a recompile - I'm talking visual effects and a couple of misaligned textures here - but they added up. They made me itch.

The one thing that did need fixing - that I couldn't fix because I had no spare LEAVES or CLIPNODES to play with - was the numerous sticky points on the walls of my volcanic crater. If you jumped out into space and slid down the rock face, you could almost count on finding a spot where you'd stick. Given the dim lighting, you could sit there and snipe people until you ran out of ammo, and nobody would ever find you. (One time when my buddy did that, I came looking for him after he ran out of ammo. Only when he turned his flashlight on could I see him, and then barely. Once he turned it off again he was completely invisible...) And given that this level already has plenty of sniper-friendly spots, it was particularly irritating.

So there I was, looking through the compile tools reference, trying to find a way to fix my misaligned textures without doing a full recompile (I couldn't find one) when I saw -cliptype precise.

To be clear on this, I was already well aware of the -cliptype setting -- I'd used precise when compiling Lab17: Storage -- and I thought I'd been using it at some point during this compile process. I actually remember changing the -cliptype mode and suddenly having a load more clipnodes to play with -- but when I checked, I discovered I wasn't currently compiling with this option.

So I turned it on.

Suddenly, instead of using 99.8% of the available clipnodes, my usage was down to 68%. I let it finish, and I tested it - and yeah, now you slide down those rock walls like a greased pig! And then it occurred to me: I had a large section of my level clipped off as unusable - eye candy only - because I just needed the clipnodes. It was the first fully detailed section I built. And now, whaddaya know? I had a load of spare clipnodes to play with. So I removed the clipping block from that section, and the level is currently compiling nicely! I'm rather excited - and at the same time, I'm opening up more playing area for a level which is already a little on the large side... Oh well...

The only thing I haven't tested is my air ducts. I know precise has a tendency to make some spaces narrower, so I hope the air ducts are still accessible. If not I'll have to scrap this whole rejuvenated compile...
darkphoenix_6815 years ago2008-10-20 07:55:06 UTC 1 comment
I'm now running the final compile on Lab11_LavaLab ... or at least I was until I remembered something I wanted to include. One quick 18-hour recompile coming up. sigh

In other news, I said something a couple of Journal posts back about tweaking the HL SDK to prevent people from using buttons (or, more importantly, health chargers) through solid walls. I made some preliminary code available online since somebody asked for it, but I finally came to the realisation that it was my revised code that was preventing one of the switches in my level from working. (I ran the level under standard HL/valve and the switch worked fine!)

I have now fixed the flaw in the code, and made it rather more streamlined in the process. I can't find a problem with what I've done (although there is bound to be one, so use at your own risk!)

My modified code is here:

http://www.petesplace.id.au/coding/no_use_through_walls.php
darkphoenix_6815 years ago2008-09-14 08:59:18 UTC 9 comments
So I volunteered to rewrite a tutorial because the original didn't work, and the original author was listed as no longer a member.

I must be crazy! :-)

Because of the constraints under which I am submitting the new tutorial - constraints which make editing a little difficult - I shall post it here first, to make sure it looks okay and nothing is spelled incorrectly!

On the count of three, paste:

Add Fog to your Mod


by darkphoenix_68

(The mod originally posted by Highlander formed the basis of my work, but as originally posted it contained several errors that prevented it from working. The TWHL Admins kindly agreed to allow me to revise his tutorial. Needless to say, without his original tutorial as a starting point, I would never have gotten this code to work as it now does. Additional credit (as originally extended by Highlander) goes to Confused, Sysop, and Cause of Death.)

[list][li]Requires Half-Life SDK 2.3, a little coding experience, and your favourite Mod compiler![/li]
[li]Assumes you know how to compile DLL files and create/revise a Mod.[/li][/list]

If you want to add fog to one or more maps in your mod, this tutorial will show you how to do it.

This fog is a visual effect, controlled by a point entity (an [ent]env_fog[/ent]) which you can insert into your level. It can be toggled on or off as required. It can be any colour you choose, and its apparent density is controlled by setting the start and end distances over which the effect is applied.

Sadly, the code also has its limitations:
[list]
[li]It only works under the OpenGL video driver; neither Software nor Direct3D are currently supported. (I did look briefly into adding Direct3D support, but since I don't actually use it - and since it never worked under the initial implementation either - I decided to leave it as an exercise for the reader! :-))[/li]
[li]Being point-based rather than brush-based, you would need some careful level design if you wanted both foggy and fog-free areas. Its switchability helps here -- for single-player levels, anyway! (I suspect brush-based fog could be developed by looking into the water-handling code; that's another exercise for another time!)[/li]
[li]The way the code handles [val]sky[/val] leaves a little to be desired, with the [ent]skybox[/ent] occasionally flickering into partial view through the fog as you turn. Again, careful level design could resolve this issue! Other than this flicker, the sky will be fully masked by your fog colour![/li]
[/list]

So, if you want to go ahead and add support for this client-based point-entity fog to your mod, read on.

General Comments



The following tutorial is quite code-intensive, and unfortunately the bbCode used here on TWHL does not preserve indentation, so it may look a little messy. However, it does work (I've tested it extensively) and it will work whether it is indented or not. Obviously, for your own sanity, you may wish to reindent as appropriate! The other artifact of the site itself of which you should be aware is that some lines of code wrap when they reach the edge of the page; it should be obvious, but watch out for it anyway!

I have scattered additional
[green]// explanatory comments[/green]
throughout the code. These form part of the tutorial, but are (obviously) not necessary to the compiler; however, I hope they will assist in explaining what is going on. (Or, I guess, make it quite clear that I do not know what is going on...)

Existing code (shown for reference and/or context) is in
[val][i]blue italics[/i][/val]
. In many cases I have given approximate line numbers; these are only approximate, because my files contain other code changes not relevant to this tutorial... If the line number does not help you locate the piece of code I am changing, your [ins]Find...[/ins] tool is your friend!

Files to be edited are shown in
[red][b]red[/b][/red]
.

Server-side: The ClientFog Class



First we need to create support for the [ent]env_fog[/ent] entity. This is done server-side (in the
dlls
directory, and the associated Project/Workspace.)

Open the
[red][b]dlls/effects.cpp[/b][/red]
file in your favourite editor and add the following code at the end of the file:
//=======================
// ClientFog
//=======================
extern int gmsgSetFog;

[green]// This is the flag defined by the env_fog entity[/green]
[/pre][pre]#define SF_FOG_STARTOFF		0x0001

LINK_ENTITY_TO_CLASS( env_fog, CClientFog );
[/pre][pre]
[green]/*
// While a CClientFog constructor is not actually necessary for
// this code, you might find use for one down the track,
// in which case it may look like this:
CClientFog *CClientFog::FogCreate( void )
{
	CClientFog *pFog = GetClassPtr( (CClientFog *)NULL );
	pFog->pev->classname = MAKE_STRING("env_fog");
	pFog->Spawn();
	return pFog;
}
/*[/green]
[/pre][pre]
[green]// Spawns our env_fog entity and sets the m_active flag.[/green]
void CClientFog :: Spawn ( void )
{
	pev->solid			= SOLID_NOT;
	pev->movetype		= MOVETYPE_NONE;
    pev->effects		= 0;
	if ( pev->spawnflags & SF_FOG_STARTOFF )
		m_active = 0;
	else
	{
		SetThink( FogThink );
		pev->nextthink = gpGlobals->time + 0.01;
		m_active = 1;
	}
}
[/pre][pre]
[green]// Reads salient values from the env_fog entity[/green]
void CClientFog :: KeyValue( KeyValueData *pkvd )
{
    if (FStrEq(pkvd->szKeyName, "startdist"))
    {
        m_iStartDist = atoi(pkvd->szValue);
        pkvd->fHandled = TRUE;
    }
    else if (FStrEq(pkvd->szKeyName, "enddist"))
    {
        m_iEndDist = atoi(pkvd->szValue);
        pkvd->fHandled = TRUE;
    }
    else
        CBaseEntity::KeyValue( pkvd );
}
[/pre][pre]
[green]// Essentially turns our fog effect on/off by sending a message to the client.
// Anything which changes m_active should force a FogThink.[/green]
void CClientFog :: FogThink ( void )
{
	if ( m_active )
	{
        MESSAGE_BEGIN( MSG_ALL, gmsgSetFog, NULL );
        WRITE_SHORT ( pev->rendercolor.x );
        WRITE_SHORT ( pev->rendercolor.y );
        WRITE_SHORT ( pev->rendercolor.z );
        WRITE_SHORT ( m_iStartDist );
        WRITE_SHORT ( m_iEndDist );
        MESSAGE_END();
    }
	else
	{
        MESSAGE_BEGIN( MSG_ALL, gmsgSetFog, NULL );
        WRITE_SHORT ( 0 );
        WRITE_SHORT ( 0 );
        WRITE_SHORT ( 0 );
        WRITE_SHORT ( 0 );
        WRITE_SHORT ( 0 );
        MESSAGE_END();
	}
}
[/pre][pre]
[green]// Called when the entity is triggered.[/green]
void CClientFog::Use( CBaseEntity *pActivator, CBaseEntity *pCaller, USE_TYPE useType, float value )
{
	if ( ShouldToggle( useType, m_active ) )
		m_active = !m_active;
	SetThink( FogThink );
	pev->nextthink = gpGlobals->time + 0.01;
}
[/pre][pre]
[green]// These macros set up the save/restore code for the CClientFog object[/green]
TYPEDESCRIPTION CClientFog::m_SaveData[] =
{
    DEFINE_FIELD( CClientFog, m_active, FIELD_BOOLEAN ),
    DEFINE_FIELD( CClientFog, m_iStartDist, FIELD_INTEGER ),
    DEFINE_FIELD( CClientFog, m_iEndDist,FIELD_INTEGER ),
};

IMPLEMENT_SAVERESTORE(CClientFog,CBaseEntity);
The declarations for the above code need to be added to
effects.h
(you'll note some of the classes defined in
effects.cpp
have their declarations in the .cpp file, but we need to make our fog visible to other files!) Place the following code at the bottom of the
[red][b]dlls/effects.h[/b][/red]
file, just above the final
[val][i]#endif[/i][/val]
line:

//=======================
// ClientFog
//=======================
class CClientFog : public CBaseEntity
{
    public:
[green]/*
// As stated above, this constructor is not needed; add if required!
        static CClientFog *FogCreate( void );
*/[/green]
        void Spawn( void );
        void KeyValue( KeyValueData *pkvd );
        void EXPORT FogThink( void );
        void Use( CBaseEntity *pActivator, CBaseEntity *pCaller, USE_TYPE useType, float value );
        int m_iStartDist;
        int m_iEndDist;
        bool m_active;
        virtual int Save( CSave &save );
        virtual int Restore( CRestore &restore );
        static TYPEDESCRIPTION m_SaveData[];
};
This gives us the code we need to initialise our fog to the values set in the [ent]env_fog[/ent] entity. However there are still a couple of minor problems to tackle...

Server-side: Fog Status Messages



Whenever a new player joins the server, it needs to send a fog status message to the client. There are three possible situations in which the client needs to be updated, and we need to cover them all:
[list]
[li]A new level is loaded;[/li]
[li]A new player joins a running multi-player server;[/li]
[li]A player respawns after dying.[/li]
[/list]

Our first problem is that we cannot directly detect a change of map. Let's change that by hooking into code which is called at every level change.

First we shall add our variable declaration to the bottom of
[red][b]dlls/globals.cpp[/b][/red]
:
DLL_GLOBAL int          gLevelLoaded;
Now we set it at the appropriate moment; let's edit
[red][b]dlls/world.cpp[/b][/red]
:

Somewhere around line #43, you will find:
[val][i]extern DLL_GLOBAL	int			gDisplayTitle;[/i][/val]
Add the following line below that:
extern DLL_GLOBAL	int			gLevelLoaded;
Now, down around line #479 you should find the
[val][i]CWorld :: Precache[/i][/val]
function, to which you need to add the following line:
[val][i]void CWorld :: Precache( void )
{
	g_pLastSpawn = NULL;[/i][/val]
	gLevelLoaded = TRUE;	[green]// Handles "level loaded" case[/green]
With that out of the way, let's handle the player spawning cases. First we'll declare our variable in
[red][b]dlls/player.h[/b][/red]
(somewhere around line #90):
[val][i]class CBasePlayer : public CBaseMonster
{
public:[/i][/val]
    BOOL                m_bRespawned; [green]// True when fog update msg needs to be sent[/green]
Now we can modify our player code to pull all of this together. Be brave; there are many small changes scattered through this next file. Open
[red][b]dlls/player.cpp[/b][/red]
-- and here we go!

First we need to find the following line (at around line #45):
[val][i]extern DLL_GLOBAL int		g_iSkillLevel, gDisplayTitle;[/i][/val]
and change it to look like this:
extern DLL_GLOBAL int		g_iSkillLevel, gDisplayTitle, gLevelLoaded;
A little further down you will find a whole list of
[val][i]int[/i][/val]
initialisation lines; look for:
[val][i]int gmsgTeamNames = 0;[/i][/val]
and add the following on the next line:
int gmsgSetFog = 0;
Just below that you will find
[val][i]LinkUserMessages[/i][/val]
function; add
gmsgSetFog
somewhere near the top, like so:
[val][i]void LinkUserMessages( void )
{
	// Already taken care of?
	if ( gmsgSelAmmo )
	{
		return;
	}[/i][/val]
	[green]// This basically registers "SetFog" as a known message to be sent to the server[/green]
	gmsgSetFog = REG_USER_MSG("SetFog", -1 );
[val][i]	gmsgSelAmmo = REG_USER_MSG("SelAmmo", sizeof(SelAmmo));[/i][/val]
Now you want to jump down to the
[val][i]CBasePlayer::PlayerDeathThink[/i][/val]
function; this will be somewhere around line #1150 to #1200. Right at the bottom of the function, just before the
[val][i]respawn[/i][/val]
call, you want to add the following:
[val][i]	//ALERT(at_console, "Respawn\n");[/i][/val]
    m_bRespawned = TRUE;	[green]// Handles "player respawned" case[/green]
[val][i]	respawn(pev, !(m_afPhysicsFlags & PFLAG_OBSERVER) );// don't copy a corpse if we're in deathcam.[/i][/val]
Down around line #2700 to #2800 is
[val][i]CBasePlayer::Spawn[/i][/val]
; add the following line right near the bottom of the function:
[val][i]	m_flNextChatTime = gpGlobals->time;[/i][/val]
	m_bRespawned = TRUE;	[green]// Handles "new player" case[/green]
[val][i]	g_pGameRules->PlayerSpawn( this );[/i][/val]
We're almost done. Now that we have detected all of the cases for which we need to send our fog status message, all we need to do is actually process that information. The best place to do that is in the
[val][i]CBasePlayer::UpdateClientData[/i][/val]
function, down around line #3800. You can probably insert the following snippet of code at any convenient point in this function; I slotted mine between the
[val][i]if ( m_iFOV != m_iClientFOV )[/i][/val]
and the
[val][i]if (gDisplayTitle)[/i][/val]
:
[val][i]
	if ( m_iFOV != m_iClientFOV )
	{
	:
	:
	}
[/i][/val]
    if ( m_bRespawned || gLevelLoaded )
    {
        CBaseEntity *pEntity = NULL;
        pEntity = UTIL_FindEntityByClassname( pEntity, "env_fog" );
        if ( pEntity )
        {
            CClientFog *pFog = (CClientFog *)pEntity;
			[green]// CClientFog::FogThink handles the actual message sending, so let's force a think[/green]
			pFog->pev->nextthink = gpGlobals->time + 0.01;
        }
		m_bRespawned = gLevelLoaded = FALSE;
    }
[val][i]
	// HACKHACK -- send the message to display the game title
	if (gDisplayTitle)
	{
	:
	:
	}
[/i][/val]
That's it for the server-side code. Before we leave the
dlls
directory, we can probably compile the server dll -- or leave it till we're fully finished; whatever works best for you!

Client-side: Incoming Messages



At this point, the server has initialised the fog, detected everything it needs to detect, and sent a fog status message hurtling through the void (sorry, bad pun!) towards the client(s). Now we just need to convince the client to catch them! To do that, we'll leave the safety of
dlls
and move over to the
cl_dlls
directory (and associated Project/Workspace!)

First we'll ask the HUD to catch the incoming message. Open
[red][b]cl_dlls/hud.cpp[/b][/red]
and look for
[val][i]__MsgFunc_GameMode[/i][/val]
at around line #130. Add the following:
[val][i]int __MsgFunc_GameMode(const char *pszName, int iSize, void *pbuf )
{
	return gHUD.MsgFunc_GameMode( pszName, iSize, pbuf );
}
[/i][/val]
int __MsgFunc_SetFog(const char *pszName, int iSize, void *pbuf )
{
    return gHUD.MsgFunc_SetFog( pszName, iSize, pbuf );
}
Further down, around line #300, you will find a whole list of
[val][i]HOOK_MESSAGE[/i][/val]
macros in no particular order. Add:
HOOK_MESSAGE( SetFog );
to the list. (I added it between
[val][i]HOOK_MESSAGE(TeamInfo);[/i][/val]
and
[val][i]HOOK_MESSAGE(Spectator);[/i][/val]
, but I don't suppose its placement is critical. It might almost be nice if the list was sorted alphabetically, but that's up to you -- and more to the point, it may break everything, so let's leave that for another time! :-))

To follow through on our changes to the
.cpp
file, open the
[red][b]cl_dlls/hud.h[/b][/red]
file; look for
[val][i]	int  _cdecl MsgFunc_Concuss( const char *pszName, int iSize, void *pbuf );[/i][/val]
somewhere near line #640, and after it add:
int  _cdecl MsgFunc_SetFog(const char *pszName, int iSize, void *pbuf );
Now that we've caught the message from the server, let's process it. To do this we need
[red][b]cl_dlls/hud_msg.cpp[/b][/red]
. Before we do anything else, let's declare the variables we're going to use. After the initial
[val][i]#include[/i][/val]
s and
[val][i]#define[/i][/val]
s, add the following:
float g_fFogColor[3];
float g_fStartDist;
float g_fEndDist;
A couple of lines down from there, we shall reset our fog distance values when we reset the HUD:
[val][i]int CHud :: MsgFunc_ResetHUD(const char *pszName, int iSize, void *pbuf )
{
	ASSERT( iSize == 0 );[/val][/i]
	g_fStartDist = 0.0;
g_fEndDist = 0.0;
Right at the bottom of the file, we'll add our
SetFog
function which actually interprets the received message:
int CHud :: MsgFunc_SetFog( const char *pszName, int iSize, void *pbuf )
{
    BEGIN_READ( pbuf, iSize );
    g_fFogColor[0] = (float)READ_SHORT(); // R
    g_fFogColor[1] = (float)READ_SHORT(); // G
    g_fFogColor[2] = (float)READ_SHORT(); // B
    g_fStartDist = (float)READ_SHORT();
    g_fEndDist = (float)READ_SHORT();
    return 1;
}

Where The Magic Happens



We're on the home straight now. We've done everything except actually display the fog. (Well, almost everything, but I'll get to that!) The file we want to edit now is
tri.cpp
. This is the file which confused me the most when I was originally trying the get the code to work -- but it was only once I started looking further afield that I solved the puzzle. Anyway, without further ado, let's open
[red][b]cl_dlls/tri.cpp[/b][/red]
. Just after the initial
[val][i]#include[/i][/val]
s and
[val][i]#define[/i][/val]
s, we'll access the variables we set up previously:
extern float g_fFogColor[3];
extern float g_fStartDist;
extern float g_fEndDist;
After the
[val][i]extern "C" { ... }[/i][/val]
declarations, we'll add the heart of the whole thing:
void RenderFog ( void )
{
	static float fColorBlack[3] = {0,0,0};
	if (g_fStartDist > 0.0 && g_fEndDist > 0.0)
		gEngfuncs.pTriAPI->Fog ( g_fFogColor, g_fStartDist, g_fEndDist, 1 );
	else
		gEngfuncs.pTriAPI->Fog ( fColorBlack, g_fStartDist, g_fEndDist, 0 );
}
And now for the thing that really confused me. At the bottom of the file we have
[val][i]HUD_DrawNormalTriangles[/i][/val]
and
[val][i]HUD_DrawTransparentTriangles[/i][/val]
. Extensive testing led me to the inescapable conclusion that each apparently does the other's job:
[val][i]HUD_DrawNormalTriangles[/i][/val]
handles "transparent" faces and
[val][i]HUD_DrawTransparentTriangles[/i][/val]
handles -- well, everything. Of course, all you really need to know is to change the latter function as follows:
[i][val]/*
=================
HUD_DrawTransparentTriangles
[/val]
[green]Despite the name, it appears that this actually controls
non-transparent entities and world brushes[/green][val]
=================
*/
void DLLEXPORT HUD_DrawTransparentTriangles( void )
{[/val][/i]
    RenderFog();
[val][i]#if defined( TEST_IT )
//	Draw_Triangles();
#endif
}[/i][/val]
That's it! Compile that, and if all goes according to plan you'll have working fog.

(Well, there's still that "almost" to take care of. I'll get there in a second. Just as an aside, though, while we're here: see that
[val][i]#if defined( TEST_IT ) ... #endif[/i][/val]
? While playing with this file I compiled in the
[val][i]Draw_Triangles()[/i][/val]
code. The whole thing (ie, Half-Life) fell over while trying to start a map, so I hastily got rid of it again. As soon as I'm done with this tutorial I shall probably clean all that code out to make the file a little neater...)

The Final Step



So what remains to be done? Adding the [ent]env_fog[/ent] entity to your map, of course. If you use Valve Hammer Editor, you can add the following to whichever FGD file you are mapping with:
@PointClass base(Targetname) size(-16 -16 -16, 16 16 16) = env_fog : "Client Fog"
[
    startdist(integer) : "Start Distance" : 1
    enddist(integer) : "End Distance" : 1500
    rendercolor(color255) : "Fog Color (R G B)" : "0 0 0"
	spawnflags(Flags) =
	[
		1 : "Start Off" 	: 0
	]
]
Since VHE does no sorting of its own, it probably makes sense to add this between [ent]env_explosion[/ent] and [ent]env_global[/ent] -- ie, alphabetically! You will now be able to add [ent]env_fog[/ent]s to your maps - although be warned, I'm not sure what will happen if you have more than one per level. I'd imagine if you had several with different colours, you could trigger them off and on to change the colour of your fog - but I have not tested that!

Unfortunately I am not familiar with any other editors used to make HL maps. No doubt they have a way of adding new entities, similar to the FGD file in Hammer -- but you'll need to figure that out for yourself! Sorry...

Wrap Up



What I have done is produce a small (and exceedingly ugly) test map for you to check out. I guess you could play with that to add multiple [ent]env_fog[/ent] entities, or whatever else you wanted to try. In the map you will find several transparent objects ("texture"-rendered and "solid"-rendered) because that was what I was having the most difficulty with initially. You will also find a large target which, if shot, will toggle the fog on and off.

It is quite a bit of coding to get to this point, but I hope my directions have been clear enough that we all arrived here with code which compiles and, y'know, actually works! If you have any problems let me know and I'll see if I can help out.

Have fun!
darkphoenix_6815 years ago2008-09-05 23:01:17 UTC 2 comments
Lab11_LavaLab is almost finished - or, perhaps more accurately, almost ready to be placed online so you guys can, uh, tear it to shreds! :) As always, if any comments come through that I think are worth applying I shall do so. For now, though, I already have a few things which I need to add, to make it as complete as possible before I unleash it.

We had our initial play-test session last Tuesday. My very first thought was that it is way too big - certainly too big for two players, which is often all we can pull together to play the darn thing! This should not have been any surprise - after all, I've maxed out on clipnodes several times, and have been struggling to keep it under the max leaf count -- and yet, somehow, the fact that I was remaking my first (and smallest) level had me thinking it would be, well, small. Go figure...

I think once we learn our way around it, it won't seem quite as bad, but I still suspect it really needs four players or more to be worth playing. Which really is a shame, because we've been pretty excited about its release for a while now. On the bright side, though, I already know how I'll be reducing (while at the same time, expanding) the playable area in Lab11_Erupt; it will be a lot more open, like Mausoleum.