Bleach:Zanpakutou Senshi is a totally free Total Conversion of Half-Life 1. It´s based on the japanese hit Anime Bleach.

Report RSS Make a teleporter exit spawn you inside an area

This tutorial will demonstrate how simple you can make a brush define the area where the player will spawn inside when entering a teleporter.

Posted by on - Intermediate Server Side Coding

Thanks and in depth information

Thanks to Cathal aka sourcemodding for making the tutorial Where is Poppy - Your First Custom Entity. Check it out to get an understanding of how to make a custom entity and how an entity is linked to the FGD used in hammer. His tutorial provided the basis knowledge for me, when I had to come up with a solution to how a teleporter should place a player within an area instead of a single point.
With the knowledge from Cathal's tutorial, you will not only be able to know what you need to use and where to look, but you will be able to do much more than what this tutorial covers. This tutorial is merely a small tweak to an existing entity, that upgrade its ability to use information from another entity and thus expand its usage.

Teleporting to a random position in an area, why?

Imagine you have a bunch of players in a competing death match, with high explosive weapons that provides a lot of damage, and combine it with a map where teleportation is necessary to move around. Wouldn't it be a shame if the players decided that camping the teleport exit would be the best strategy?

Imagine you have a huge teleportation field, on both the entry and exit side. Would it make sense that you apparently appear the same place, regardless of where you enter the teleporter?
Maybe it might even make sense that you appear at the exit relative to where you entered?

These are just a pair of reasons for why you might want to make a new type of teleporter or simply modify the behaviour of the existing teleporter.

It is really simple

At BZSmod we have a map that requires teleportation to reach the high altitude platform in one of our maps, and it would be sad if it were to be camped. Furthermore the "teleporters" in the anime which BZSmod is based on are really huge, so it would be odd for us to place small teleport pads instead.

Here is how we defined our teleporters:

Teleporter


The quick observer will notice from the detailed definition above, that the exit and entry is not considered to be inside of each other or the same entity.
In our case (and due to technicalities most likely also in your case), defining that the entry and exit should be the same entity would limit or complicate matters, by either requiring to be bi-directional or a set of parameter for what each entity should target would be required. But then again, one entity is not supposed to know about another entity when you are designing your map unless it is really necessary.

Instead we decided that the placement of the entry should be placed closest to the visual area of the teleporter and the exit should be placed further away from the visual cue.
I must admit that we didn't test the setup where the teleporter is in a center and exiting could be all the way around, in general, we don't know what happens if you spawn inside the teleport entry.
However you can get around it by making 4 entries around the visual cue and 4 exits a little further out.

Here is how we did it

A brush in Hammer tools is like a simple game entity(assumption), at least you can take a brush and assign an entity to it in hammer tools, which provides the properties of that entity to the brush.
Ex:

  • Trigger_teleport will make the brush invisible and teleport the player to the origin of the target entity that was provided to it in its list of properties, when the player touches it.
  • Func_wall will be a solid wall, however you get access to some additional render properties.
  • Func_illusionary is just like Func_wall, except that is isn't solid, so the player can be inside of it.

Additionally in code, these entities will hold the origin of the entity and its boundary box.

Upon further inspection of the Trigger_teleport, which class is named CTriggerTeleport, we found that the touch function CBaseTrigger::TeleportTouch which takes care of moving the player to the new position, doesn't care about what type of entity it get its information from.
So while the standard approach would be to use a info_teleport_destination (which is a CPointEntity), the target might as well be a Func_illusionary. And that is what makes this tutorial so simple.

Example of the TrigerTeleport spawn function that sets the touch function:

void CTriggerTeleport::Spawn( void )
{
 InitTrigger();

 SetTouch( &CBaseTrigger::TeleportTouch );
}


The touch function is defined as:

void CBaseTrigger :: TeleportTouch( CBaseEntity *pOther )
{
 entvars_t* pevToucher = pOther->pev;
 edict_t *pentTarget = NULL;

 /* Omitted code with flag checks and safety checks */
 
 pentTarget = FIND_ENTITY_BY_TARGETNAME( pentTarget, STRING(pev->target) );
 if (FNullEnt(pentTarget))
    return; 
 Vector tmp = VARS( pentTarget )->origin;

 if ( pOther->IsPlayer() )
 {
  tmp.z -= pOther->pev->mins.z;// make origin adjustments in case the teleportee is a player. (origin in center, not at feet)
 }

 tmp.z++;

 pevToucher->flags &= ~FL_ONGROUND;
 
 UTIL_SetOrigin( pevToucher, tmp );

 pevToucher->angles = pentTarget->v.angles;

 if ( pOther->IsPlayer() )
 {
  pevToucher->v_angle = pentTarget->v.angles;
 }

 pevToucher->fixangle = TRUE;
 pevToucher->velocity = pevToucher->basevelocity = g_vecZero;
}

Notice how we find an entity by name, followed by copying the target's entity origin position.

 pentTarget = FIND_ENTITY_BY_TARGETNAME( pentTarget, STRING(pev->target) );
 if (FNullEnt(pentTarget))
    return; 
 Vector tmp = VARS( pentTarget )->origin;

The current code originally doesn't care what type of entity the target is, and the teleportation is simply putting the player at the position of the provided origin from the target.
So instead of creating an entire new entity to teleport us to a random position inside a target brush, we can just expand this code to take the brush info, if the target is from a brush entity.
There are multiple ways to do this, but we specifically went with the Func_illusionary to make it simple for us and benefit from the additional render options.
To do so we add:

 Vector tmp; //add to the top of the function
/* [....]*/
 
        if (FNullEnt(pentTarget))
    return; 
 if (strncmp(STRING(pentTarget->v.classname), "func_illusionary",17) == 0)
 {
  // Brush version, get random position from brush world vector.
  tmp.x = RANDOM_LONG(pentTarget->v.mins.x, pentTarget->v.maxs.x);
  tmp.y = RANDOM_LONG(pentTarget->v.mins.y, pentTarget->v.maxs.y);
  tmp.z = pentTarget->v.mins.z;
 }
 else
 {
  tmp = VARS(pentTarget)->origin; // CPointEntity is usually at ground (z = ground), world vector
 }

Note that the vector tmp declaration has been moved ot the top of the function.
This code checks if the target is a Func_illusionary and calculates a random x and y position within the brush's area.
The z position is set to the bottom of the brush, as it corresponds with the position in a point entity. The if case ensures that only a Func_illusionary triggers the randomization, but it could technically be any brush entity.
Please note that a brush entity will hold an origin of [0,0,0], so you must use min and max to calcuiate the position. A point entity on the other hand uses the origin, and should not be calculated from min and max.

The function now looks like this:

void CBaseTrigger :: TeleportTouch( CBaseEntity *pOther )
{
 entvars_t* pevToucher = pOther->pev;
 edict_t *pentTarget = NULL;
 Vector tmp;

 // Only teleport monsters or clients
 if ( !FBitSet( pevToucher->flags, FL_CLIENT|FL_MONSTER ) )
  return;
    
 if (!UTIL_IsMasterTriggered(m_sMaster, pOther))
  return;
  
 if ( !( pev->spawnflags & SF_TRIGGER_ALLOWMONSTERS ) )
 {// no monsters allowed!
  if ( FBitSet( pevToucher->flags, FL_MONSTER ) )
  {
   return;
  }
 }

 if ( ( pev->spawnflags & SF_TRIGGER_NOCLIENTS ) )
 {// no clients allowed
  if ( pOther->IsPlayer() )
  {
   return;
  }
 }
 
 pentTarget = FIND_ENTITY_BY_TARGETNAME( pentTarget, STRING(pev->target) );
 if (FNullEnt(pentTarget))
    return; 
 if (strncmp(STRING(pentTarget->v.classname), "func_illusionary",17) == 0)
 {
  // Brush version, get random position from brush world vector.
  tmp.x = RANDOM_LONG(pentTarget->v.mins.x, pentTarget->v.maxs.x);
  tmp.y = RANDOM_LONG(pentTarget->v.mins.y, pentTarget->v.maxs.y);
  tmp.z = pentTarget->v.mins.z;
 }
 else
 {
  tmp = VARS(pentTarget)->origin; // CPointEntity is usually at ground (z = ground), world vector
 }

 if ( pOther->IsPlayer() )
 {
  /* player mins are localvector, so -(-36) will lift the z value to fit to character origin */
  tmp.z -= pOther->pev->mins.z;// make origin adjustments in case the teleportee is a player. (origin in center, not at feet)
 }

 tmp.z++;

 pevToucher->flags &= ~FL_ONGROUND;
 
 UTIL_SetOrigin( pevToucher, tmp );

 pevToucher->angles = pentTarget->v.angles;

 if ( pOther->IsPlayer() )
 {
  pevToucher->v_angle = pentTarget->v.angles;
 }

 pevToucher->fixangle = TRUE;
 pevToucher->velocity = pevToucher->basevelocity = g_vecZero;
}

In Hammer tools you now place a brush, convert it to an entity and select Func_illusionary. You provide a name for it in the Name property.

teleport tut 1

Somewhere else you then place a brush, which you convert to an Trigger_teleport entity and in its Target you provide the name of the Func_illusionary brush.

teleport tut 2

You should get a similar result as shown in the teleport demo video.

That concludes this tutorial.

The HTML for the code snippets were generated at Hilite.me

Post comment Comments
eliasr Author
eliasr - - 515 comments

A small error has been found in regards to the origin of the teleport target, the article will be updated soon.

Reply Good karma+1 vote
eliasr Author
eliasr - - 515 comments

The origin code has been corrected.
It has also been emphasized what the brush entity use to define its position compared to an point entity.

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: