• Register
Post tutorial Report RSS Quake-c - Touch

Using the .touch() function call to get down with quake entities.

Posted by on - Advanced Server Side Coding

What is .touch() ?

Quake-c definitions in defs.qc:

.void() touch;
float force_retouch; // force all entities to touch triggers

// model parmeters
.float modelindex; // *** model index in the precached list
.string model;

// bounding box size
.vector mins, maxs;

.float solid;

// edict.solid values
float SOLID_NOT = 0; // no interaction with other objects
float SOLID_TRIGGER = 1; // touch on edge, but not blocking
float SOLID_BBOX = 2; // touch on edge, block
float SOLID_SLIDEBOX = 3; // touch on edge, but not an onground
float SOLID_BSP = 4; // bsp clip, touch on edge, block

.float flags;

float FL_ITEM = 256;

// builtins that effect touch funciton

void(entity e, string m) setmodel = #3;
void(entity e, vector min, vector max) setsize = #4;


Yes, these all can affect .touch().

Touch functions are one of the more complex quake-c concepts because they require so many elements set properly to function correctly.


How does it work?

.touch() is a per entity void() function pointer as defined in defs.qc.
Every entity has the "possibility" of a touch function call when its bounding box touches:

- another "visible" (has model set) entity bounding box
- an "invisible" (i.e. has no visible model) trigger field bounding box
- the surfaces or fluids of the world entity


The procedure:

1 - you need two entities (this includes the world and unseen entities)
2 - both must have some .solid set greater than SOLID_NOT = 0
3 - one must declare a valid function call in .touch (such as health_touch() )
4 - both must have a valid bounding box (boxes are set by setmodel and setsize)
5 - contact between bounding boxes must occur

When this sequence is complete - the quake engine code calls the entity .touch() function with:

self = the entity whose .touch() is being called
other = the touching entity

Typical entities that use the touch function call:

- Collectable artifacts, items, weapons and ammo
- Missile objects
- Doors, plats and buttons
- Trigger fields - teleporters, traps, damage fields
- Message delivery
- Monsters (jump touch damage on dog, fiend, tarbaby, etc.)


How do setmodel and setsize affect .touch().

.model is a per entity string field that can reference one of the pre-cache string values for a .mdl format file.
.mins and .maxs are per entity vector values that define "unseen" bounding boxes for entities.

When you call setmodel from quake-c several actions are performed by the engine.

- if the model string is valid, the entity will be assigned the appearance of that model
- if the model string is not valid or null ("") the entity will not appear
- changing .modelindex has the same effect
- a default "setsize()" occurs - using the largest size dimensions of all the models frames
- the entity appears as this model at its current .origin vector in map space
- if setsize is called the bounding box is set to those vector dimensions from '0 0 0' in the model
- the touch area of the model is the .mins and .maxs bounding box


How does .solid affect touch?

.solid is a per entity float value the engine uses to determin how entities interact.

This chart indicates what the engine usually does with entities with various .solid values:

SOLID_NOT - not solid, will not activate .touch()
SOLID_TRIGGER - activates .touch(), other solids can move through - items, triggers, messages
SOLID_BBOX - .touch() at edge, blocks solids - missiles, explode boxes
SOLID_SLIDEBOX - .touch() at edge, blocks solids - players, monsters
SOLID_BSP - .touch() at edges, blocks solids as world brushes - doors, plats, buttons

These are tricky. Assigning the wrong value can have odd and even disasterous affects in game.


How does .flags affect touch?

.flags is a per entity float value with bit values used by the engine and quake-c.

When .flags has the FL_ITEM bit set the entity has a much larger bounding box.
This was done to make it easier for fast moving players to collect items, weapons, ammo and artifacts.


How can you use it?

Code examples by function contents - for a health box:

item_health() // map spawn function

self.touch = health_touch; // call this when touched
setmodel(self, "maps/b_bh25.bsp");
setsize (self, '0 0 0', '32 32 56');
StartItem (); // think set to PlaceItem() in 0.2 secs so other map solids get set

PlaceItem()

self.mdl = self.model; // so it can be restored on respawn
self.flags = FL_ITEM; // make extra wide
self.solid = SOLID_TRIGGER; // allow it to be touched

health_touch() // called by engine when bounding box is touched

// self is the health box

if (other.classname != "player") // only player entites touch health boxes - this is an exclusion test
return;
// beyond the exclusion, other is a player entity

self.model = string_null; // hide it
self.solid = SOLID_NOT; // no touching
self.nextthink = time + 20; // done if deathmatch is true
self.think = SUB_regen;

SUB_regen()

self.model = self.mdl; // restore original model
self.solid = SOLID_TRIGGER; // allow it to be touched again
setorigin (self, self.origin);

The above code fragments represent a complete touch cycle for a typical collectable item.
Note: there are many other aspects of touchable items - the above bits represent those that affect .touch();


Troubleshooting:

Made a new (weapon, missile, door, trigger, etc.) and it doesnt seem to work?

Check .solid. Thats the first thing, make sure its set to the apropriate value. 90% of touch failures are SOLID_NOT.
Check .touch - make sure its calling the function you want and then check that functions exclusion logic.

Having problems getting a new item?

Make sure .flags has the FL_ITEM bit set.
Check the bounding box vectors (and with darkplaces, at the console enter: r_showbboxes 1) - see if it has a touchable box.

Solid dimensions seem to be wrong?

If you cant get near a SOLID_BBOX (or above) object, or you can move through its edges...
Check .mins and .maxs vectors. setmodel() gives a default bounding box based on the max size found in all model frames.
setsize() could also be called with the wrong parameters.


Caveats:

"force_retouch" - a system global the engine uses to re-check all touch fields. If you create a new or re-activate an old trigger field - then set this to 2 and the engine will check all touch possibilities.

Touching may need debounced - .touch() can be called every frame that bounding boxes are in contact.
One method of doing this for collectable items is to set solid to SOLID_NOT, then restore SOLID_TRIGGER on respawn.

Flying and bouncing missiles (rockets, grenades, nails, lasers, etc.) have no bounding box.
All missiles have setsize(self, '0 0 0', '0 0 0') - .touch() is called when the center of the missile model contacts another solid entity at SOLID_BBOX or higher value .solid. If you give a missile a bounding box they can have odd impacts on world brushes and such.

Doors, plats and buttons that are not targeted by a trigger field or other entity, or have health and take damage, have an engine spawned "default" trigger field.


Visual examples:

Bounding boxes as seen in darkplaces with: r_showbboxes 2

white = SOLID_NOT
pink = SOLID_TRIGGER
green = SOLID_BBOX
red = SOLID_SLIDEBOX
blue = SOLID_BSP

figure 1 - "r_showbboxes 1" - full size link: Media.moddb.com
Post comment Comments
WeeGee9000
WeeGee9000 - - 1,639 comments

Im curious. What level editor did you use at the end of the article? Looks like Doom Builder, but with Quake

Reply Good karma Bad karma+2 votes
numbersix Author
numbersix - - 2,244 comments

A question. Since long before your sun burned in the heavens I have awaited a question. Heh heh.
I use GTK-Radiant 1.6.2.
Some config files I eventually plan to release let me do all kind of cool stuff with radiant.

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: