• entries
    222
  • comments
    608
  • views
    588271

Entities Spawned by QuakeC Code

Sign in to follow this  
benryves

1344 views

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(e.Filename)));
break;
case ".bsp":
if (CachedLevels.ContainsKey(e.Filename)) return; // Already cached.
CachedLevels.Add(e.Filename, new Renderer.BspRenderer(this.Resources.LoadObject(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 key
In order for keys to work
you MUST set your maps
worldtype to one of the
following:
0: medieval
1: metal
2: 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.
Sign in to follow this  


4 Comments


Recommended Comments

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...

Share this comment


Link to comment
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]

Share this comment


Link to comment
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).

Share this comment


Link to comment

Create an account or sign in to comment

You need to be a member in order to leave a comment

Create an account

Sign up for a new account in our community. It's easy!

Register a new account

Sign in

Already have an account? Sign in here.

Sign In Now