One day, 14-year-old me wanted to make a huge, giant map. However, he couldn't. Entities stopped working as soon as they reached 4096 units in any of the axes.

You've all heard of the infamous limitation that entities can't go beyond +-4096 units, and players can't go beyond +-8192 units. Believe it or not, this is

It's very simple and straightforward to do, so let's get straight into it.

`CBaseEntity::IsInWorld()`

in cbase.cpp.
```
BOOL CBaseEntity :: IsInWorld( void )
{
// position
if (pev->origin.x >= 4096) return FALSE;
if (pev->origin.y >= 4096) return FALSE;
if (pev->origin.z >= 4096) return FALSE;
if (pev->origin.x <= -4096) return FALSE;
if (pev->origin.y <= -4096) return FALSE;
if (pev->origin.z <= -4096) return FALSE;
// speed
if (pev->velocity.x >= 2000) return FALSE;
if (pev->velocity.y >= 2000) return FALSE;
if (pev->velocity.z >= 2000) return FALSE;
if (pev->velocity.x <= -2000) return FALSE;
if (pev->velocity.y <= -2000) return FALSE;
if (pev->velocity.z <= -2000) return FALSE;
return TRUE;
}
```

This is where you'll make the first edit. You might wanna either define a macro, or a constexpr int, or just replace the numbers, for example, 65536 instead of 4096.IsInWorld() is generally called by some entities that have the potential of falling out of the map, like through the skybox.

However, not all such entities call IsInWorld().

In controller.cpp, around line 1210:

```
// check world boundaries
if (gpGlobals->time - pev->dmgtime > 5 || pev->renderamt < 64 || m_hEnemy == NULL || m_hOwner == NULL || pev->origin.x < -4096 || pev->origin.x > 4096 || pev->origin.y < -4096 || pev->origin.y > 4096 || pev->origin.z < -4096 || pev->origin.z > 4096)
{
SetTouch( NULL );
UTIL_Remove( this );
return;
}
```

In apache.cpp, around line 1030:
```
// check world boundaries
if (pev->origin.x < -4096 || pev->origin.x > 4096 || pev->origin.y < -4096 || pev->origin.y > 4096 || pev->origin.z < -4096 || pev->origin.z > 4096)
{
UTIL_Remove( this );
return;
}
```

It would be a good idea to replace these pev->origin comparisons with a single IsInWorld() call.However, this is not done yet.

```
clientdata_t none
{
DEFINE_DELTA( flTimeStepSound, DT_INTEGER, 10, 1.0 ),
DEFINE_DELTA( origin[0], DT_SIGNED | DT_FLOAT, 21, 128.0 ),
DEFINE_DELTA( origin[1], DT_SIGNED | DT_FLOAT, 21, 128.0 ),
DEFINE_DELTA( velocity[0], DT_SIGNED | DT_FLOAT, 16, 8.0 ),
DEFINE_DELTA( velocity[1], DT_SIGNED | DT_FLOAT, 16, 8.0 ),
```

Hmm hmm hmm.clientdata_t defines encoding for the local player.

The flags DT_SIGNED and DT_FLOAT are pretty much self-explanatory, however, what we're truly interested in are the two numbers: 21 and 128.

21 is the number of bits being transmitted for origin.x, for example. And at the precision of 1/128 of a unit.

So, hmm, what does this mean? What's the maximum value origin.x could have?

2^21 is 2'097'152. Divided by 128, it's 16384. Wow! That exactly matches the range that the player can move in, which is -8192 units to +8192 units.

From this, we can imagine a formula which we'll use to calculate the number of bits we need:

`sign bit + value bits + precision bits`

Let's imagine we want +-8192 units at 1/128 precision.

Precision bits is 7 because 128 is 2^7. Sign bit is always 1. Value bits is 13 because 8192 is 2^13.

When you add these up, you get 21, just like the original value in delta.lst

Here's a cheat sheet, assuming you won't change the precision (128.0) to any other value:

21 -> +-8192

22 -> +-16384

23 -> +-32768

24 -> +- 65536

25 -> +-131072

You probably don't need to go any further than that, especially for multiplayer, where every bit counts. You'll generally replace the number of bits for origin[0], origin[1] and origin[2].

Now, that was for clientdata_t. Let's move on to entity_state_t:

```
entity_state_t gamedll Entity_Encode
{
DEFINE_DELTA( animtime, DT_TIMEWINDOW_8, 8, 1.0 ),
DEFINE_DELTA( frame, DT_FLOAT, 10, 4.0 ),
DEFINE_DELTA( origin[0], DT_SIGNED | DT_FLOAT, 16, 8.0 ),
DEFINE_DELTA( angles[0], DT_ANGLE, 16, 1.0 ),
DEFINE_DELTA( angles[1], DT_ANGLE, 16, 1.0 ),
DEFINE_DELTA( origin[1], DT_SIGNED | DT_FLOAT, 16, 8.0 ),
DEFINE_DELTA( origin[2], DT_SIGNED | DT_FLOAT, 16, 8.0 ),
```

This encoding is for non-player entities.Using the same formula, let's call it that way, we can calculate how far these can go.

2^16 is 65536, divided by 8 is 8192. So +-4096.

Here's a cheat sheet for these too:

18 -> +-16384

19 -> +-32768

20 -> +- 65536

21 -> +-131072

Ultimately, you must change origin[0], origin[1] and origin[2] bits here:

```
entity_state_player_t gamedll Player_Encode
{
DEFINE_DELTA( animtime, DT_TIMEWINDOW_8, 8, 1.0 ),
DEFINE_DELTA( frame, DT_FLOAT, 8, 1.0 ),
DEFINE_DELTA( origin[0], DT_SIGNED | DT_FLOAT, 18, 32.0 ),
DEFINE_DELTA( angles[0], DT_ANGLE, 16, 1.0 ),
DEFINE_DELTA( angles[1], DT_ANGLE, 16, 1.0 ),
DEFINE_DELTA( origin[1], DT_SIGNED | DT_FLOAT, 18, 32.0 ),
DEFINE_DELTA( origin[2], DT_SIGNED | DT_FLOAT, 18, 32.0 ),
```

This is for players other than the local player.To calculate how many bits you need for these, I'll leave that as an exercise to the reader.

For real.

Now you can go a long way in your maps, literally.

Video, proof of this concept

However, one last thing remains.

The original VHLT code is damn messy, with tons of #ifdefs and whatnot.

In the hlcsg project, in csg.h, you'll find this macro:

`#define BOGUS_RANGE 65534`

You may change that as you wish. For example, 262144. I added a parameter to the compiler so the mapper can choose, but I won't cover that in this tutorial. Same thing goes for this line inside bspfile.h:

`#define ENGINE_ENTITY_RANGE 4096.0`

Alternatively, if you don't want to modify the compilers, you can use the Sven Co-op compilers, or my VHLT version.
There are just a couple of things that don't work well in this new range. Precisely, it is the TE_ effects.

This means explosion sprites and decals, from grenades, rockets etc. will not show up where you expect them to. They will clamp back to 0,0,0.

Also pay attention to weapons, NPC smell and some other details, as weapons perform UTIL_TraceLine either 4096 or 8192 units in the aim direction. But, I suppose this won't be really necessary to change unless you're making a sniper mod.

Potentially, AI nodes may also not work very well as they're designed with +-8192 units in mind.

```
// Convert from [-8192,8192] to [0, 255]
//
inline int CALC_RANGE(int x, int lower, int upper)
{
return NUM_RANGES*(x-lower)/((upper-lower+1));
}
```

This may need some changes. But otherwise, ANY entity is gonna work outside of 8192 units now. Also, one word for level designers/mappers: it's important to understand that all the other limits still apply. So if you want to make a feasible map that is still 32k x 32k units in area or larger, you'll have to make them really low-detail in places, or use high texture scales, and definitely save on those clipnodes.

We'll see how to work around some of these 'bugs' in the next part of this tutorial, which, I'll likely write this summer. There will be a tutorial about mapping too, explaining some techniques for making huge maps.

Until then, happy programming!

-Admer

- Article Credits
- Admer456 – Original author
- -Step4enko- – Special thanks

barney

Commented 1 month ago2020-04-25 22:45:15 UTC
Comment #102686

Thank you for this tutorial!

You must log in to post a comment. You can login or register a new account.