Entities Spawned by QuakeC Code

Published September 06, 2007
After all that time spent trying to work out how the QuakeC VM works I finally have some real-world results. [smile]

Apart from the obvious boring stuff going on in the background parsing and loading entities go, two functions in particular are of note. The first is a native function, precache_model(string) which loads and caches a model of some description (sprites, Alias models or BSP models). The QuakeC VM I've written raises an event (passing an event containing the name of the model to load), which the XNA project can interpret and use to load a model into a format it's happy with.


Inspiration for the Strogg in Quake 2?


With a suitable event handler and quick-and-dirty model renderer in place, the above models are precached (though they probably shouldn't be drawn at {0, 0, 0}).

The second function of interest is another native one, makestatic(entity). Quake supports three basic types of entity - dynamic entities (referenced by an index, move around and can be interacted with - ammo boxes, monsters and so on), temporary entities (removes itself from the game world automatically - point sprites) and static entities. Static entities are the easiest to handle - once spawned they stay fixed in one position, and can't be accessed (and hence can't be removed). Level decorations such as flaming torches are static. Here's the QuakeC code used to spawn a static small yellow flame:

void() light_flame_small_yellow ={	precache_model ("progs/flame2.mdl");	setmodel (self, "progs/flame2.mdl");	FireAmbient ();	makestatic (self);};
That ensures that the model file is precached (and loaded), assigns the model to itself, spawns an ambient sound (via the FireAmbient() QuakeC function) then calls makestatic() which spawns a static entity then deletes the source entity. In my case this triggers an event that can be picked up by the XNA project:

// Handle precache models:void Progs_PrecacheModelRequested(object sender, Quake.Files.QuakeC.PrecacheFileRequestedEventArgs e) {	switch (Path.GetExtension(e.Filename)) { 		case ".mdl":			if (CachedModels.ContainsKey(e.Filename)) return; // Already cached.			CachedModels.Add(e.Filename, new Renderer.ModelRenderer(this.Resources.LoadObject<Quake.Files.Model>(e.Filename)));			break;		case ".bsp":			if (CachedLevels.ContainsKey(e.Filename)) return; // Already cached.			CachedLevels.Add(e.Filename, new Renderer.BspRenderer(this.Resources.LoadObject<Quake.Files.Level>(e.Filename)));			break;	}}// Spawn static entities:void Progs_SpawnStaticEntity(object sender, Quake.Files.QuakeC.SpawnStaticEntityEventArgs e) {	// Grab the model name from the entity.	string Model = e.Entity.Properties["model"].String;	// Get the requisite renderer:	Renderer.ModelRenderer Renderer;	if (!CachedModels.TryGetValue(Model, out Renderer)) throw new InvalidOperationException("Model " + Model + " not cached.");	// Add the entity's position to the renderer:	Renderer.EntityPositions.Add(new Renderer.EntityPosition(e.Entity.Properties["origin"].Vector, e.Entity.Properties["angles"].Vector));}



The result is a light sprinkling of static entities throughout the level.



As a temporary hack I just iterate over the entities, checking if each one is still active, and if so lumping them with the static entities.



If you look back a few weeks you'd notice that I already had a lot of this done. In the past, however, I was simply using a hard-coded entity name to model table and dumping entities any old how through the level. By parsing and executing progs.dat I don't have to hard-code anything, can animate models correctly, and even have the possibility of running the original game logic.

An example of how useful this is relates to level keys. In some levels you need one or two keys to get to the exit. Rather than use the same keys for each level, or use many different entity classes for keys, the worldspawn entity is assigned a type (Mediaeval, Metal or Base) and the matching key model is set automatically by the key spawning QuakeC function:

/*QUAKED item_key2 (0 .5 .8) (-16 -16 -24) (16 16 32)GOLD keyIn order for keys to workyou MUST set your mapsworldtype to one of thefollowing:0: medieval1: metal2: base*/void() item_key2 ={	if (world.worldtype == 0)	{		precache_model ("progs/w_g_key.mdl");		setmodel (self, "progs/w_g_key.mdl");		self.netname = "gold key";	}	if (world.worldtype == 1)	{		precache_model ("progs/m_g_key.mdl");		setmodel (self, "progs/m_g_key.mdl");		self.netname = "gold runekey";	}	if (world.worldtype == 2)	{		precache_model2 ("progs/b_g_key.mdl");		setmodel (self, "progs/b_g_key.mdl");		self.netname = "gold keycard";	}	key_setsounds();	self.touch = key_touch;	self.items = IT_KEY2;	setsize (self, '-16 -16 -24', '16 16 32');	StartItem ();};



Mediaeval Key



Metal Runekey



Base Keycard


One problem is entities that appear at different skill levels. Generally higher skill levels have more monsters, but there are other level design concerns such as swapping a strong enemy for a weaker one in the easy skill mode. In deathmatch mode entities are also changed - keys are swapped for weapons, for example. At least monsters are kind - their spawn function checks the deathmatch global and they remove themselves automatically, so adding the (C#) line Progs.Globals["deathmatch"].Value.Boolean = true; flushes them out nicely.

Each entity, however, has a simple field attached - spawnflags - that can have bits set to inhibit the entity from spawning at the three different skill levels.


Easy



Medium



Hard


Regrettably, whilst the Quake 1 QuakeC interpreter source code is peppered with references to Quake 2 it would appear that Quake 2 used native code rather than QuakeC to provide gameplay logic, so I've dropped development on the Quake 2 side at the moment.
0 likes 4 comments

Comments

Comments are shown oldest first so the discussion reads top to bottom.
Evil Steve
Quote:
Original post by benryves
Regrettably, whilst the Quake 1 QuakeC interpreter source code is peppered with references to Quake 2 it would appear that Quake 2 used native code rather than QuakeC to provide gameplay logic, so I've dropped development on the Quake 2 side at the moment.
Really? What sort of references? And yeah, Quake II uses a DLL (game86.dll or something?) for all of the "game" logic.

This is looking really nice, I really need to get around to writing a .bsp loader - so far I only have a md2 -> custom model format and a custom loader / renderer / animating thingy...
September 06, 2007 06:33 PM
benryves
Quote:
Original post by Evil Steve
Really? What sort of references?
Nothing very special, mainly minor changes to existing functions or additional built-in functions in pr_cmds.c - sin(), cos(), sqrt(), changepitch(), TraceToss(), etos(), and WaterMove() - within #ifdef QUAKE2 blocks.
Quote:
This is looking really nice, I really need to get around to writing a .bsp loader - so far I only have a md2 -> custom model format and a custom loader / renderer / animating thingy...
Thanks; I've been wondering whether it's worth documenting some of the bits that the Unofficial Quake Specs are a bit vague on (or don't mention at all), or whether it's a case that it's too old now that nobody really cares. [rolleyes]

If the game logic is in its own DLL for Quake 2, that might be usable with some P/Invoke magic. [smile]
September 07, 2007 04:50 AM
Evil Steve
Ah, it's gamex86.dll.

Dumpbin says there's one export, GetGameAPI, and ISTR from the Quake II source that it takes a struct of function pointers (engine interface), and returns a struct of function pointers (game interface).
September 07, 2007 08:47 AM
benryves
Thanks for the information. [smile]
September 07, 2007 09:41 AM
You must log in to join the conversation.
Don't have a GameDev.net account? Sign up!