-
Notifications
You must be signed in to change notification settings - Fork 31
Merging workflow
Let's say you want to merge echoes14.bsp and echoes14b.bsp together. First, try merging without any special options.
bspguy merge test.bsp -maps "echoes14, echoes14b"
After the process finishes, you'll see some stats about the new map file.
Data Type Current / Max Fullness
------------ ------------------- --------
models 153 / 4096 3.7%
planes 14831 / 65535 22.6%
vertexes 31226 / 65535 47.6%
nodes 11647 / 32768 35.5%
texinfos 7091 / 32767 21.6%
faces 20846 / 65535 31.8%
clipnodes 33577 / 32767 102.5% (OVERFLOW!!!)
leaves 7537 / 65536 11.5%
marksurfaces 28153 / 65536 43.0%
surfedges 98479 / 512000 19.2%
edges 52964 / 512000 10.3%
textures 272 / 4096 6.6%
lightdata 2.6 / 48.0 MB 5.4%
visdata 2.7 / 8.0 MB 34.0%
entities 1760 / 8192 21.5%
In this case, there were too many clipnodes in each map and they don't fit in the result file. They almost fit though. Read below to see what can be done about this.
First, try adding the -optimize
option. If that's not enough, then also add -nohull2
. If that's still not enough, then see the Unused Model Hulls and Simplify Model Hulls sections.
If those options were enough, but collision is broken for some things, then read on to understand why.
The easiest way to reduce clipnodes is to try adding the -nohull2
option while merging.
bspguy merge test.bsp -maps "echoes14, echoes14b" -nohull2
Data Type Current / Max Fullness
------------ ------------------- --------
...
clipnodes 23121 / 32767 70.6%
...
Problem solved! However, you usually shouldn't use this option for maps that have large monsters or large func_pushables. If you do, collision will be inaccurate for those entities.
Let's say one of the maps being merged did have large monsters, and the other didn't. In that case you can strip hull 2 from one of the maps before doing the merge, and still get a large reduction in clipnodes.
bspguy noclip echoes14 -hull 2 -o echoes14_nohull2
bspguy merge test.bsp -maps "echoes14_nohull2, echoes14b"
Data Type Current / Max Fullness
------------ ------------------- --------
...
clipnodes 28473 / 32767 86.9%
...
Now let's say that both maps have large monsters. Read the next section to see how to deal with that.
The -optimize
flag conditionally deletes model hulls that appear to be unused. For example, some invisible entities like trigger_once
don't always need hull 0 (used for rendering as well as collision), and some entities like func_illusionary
don't need any clipnodes. -optimize
will also delete hull 2 if there are no large monsters or pushables in the map.
The drawback to using this command is that it's possible for entities to change state in such a way that the deleted hulls are needed later. For instance, if a func_illusionary
model is used with trigger_createentity
to create a func_wall
, then the func_wall
would have no collision because -optimize
would have deleted all collision hulls for the func_illusionary
model. Similarly, it's possible for the game to crash if -optimize
deletes hull 0 for an invisible entity, and that entity is later made visible due to a script or some entity logic.
If you use this option, you might also want to use the -v
flag to see which models/entities had their hulls stripped. It might help debug problems with entity collision or crashing.
If you can't delete all of hull 2 (or if that's not enough) then you need to start selectively removing clipnodes from specific models in the map. The info
command will tell you which models are worth considering.
bspguy info echoes14 -limit clipnodes
Classname Targetname Model Clipnodes Usage
------------------------- ------------------------- ----- ---------- --------
worldspawn *0 10733 66.0%
func_rotating *34 360 2.2%
func_rotating *22 359 2.2%
func_rotating *35 359 2.2%
func_rotating *38 359 2.2%
func_rotating *79 355 2.2%
func_rotating *10 346 2.1%
func_rotating *11 346 2.1%
func_rotating *24 346 2.1%
func_wall *62 206 1.3%
Nothing can be done about worldspawn, but those func_rotating entities look like good candidates. After opening the map in BSP Viewer you can see that those are the ceiling fans in the garage area.
There's no way a large monster is going to be able to reach these fans normally, so let's delete hull 2 on these specific fan models, and try merging again.
(you might want to start using a script to run these commands)
copy echoes14.bsp echoes14_temp.bsp
bspguy noclip echoes14_temp -hull 2 -model 34
bspguy noclip echoes14_temp -hull 2 -model 22
bspguy noclip echoes14_temp -hull 2 -model 35
bspguy noclip echoes14_temp -hull 2 -model 38
bspguy noclip echoes14_temp -hull 2 -model 79
bspguy noclip echoes14_temp -hull 2 -model 10
bspguy noclip echoes14_temp -hull 2 -model 11
bspguy noclip echoes14_temp -hull 2 -model 24
bspguy merge test.bsp -maps "echoes14_temp, echoes14b"
Data Type Current / Max Fullness
------------ ------------------- --------
...
clipnodes 32653 / 32767 99.7%
...
Just barely!
Out of curiosity, let's check the other map:
bspguy info echoes14b -limit clipnodes
Classname Targetname Model Clipnodes Usage
------------------------- ------------------------- ----- ---------- --------
worldspawn *0 11949 69.0%
func_train jet1 *39 1479 8.5%
func_train jet2 *17 1474 8.5%
func_train tonk *42 971 5.6%
func_illusionary to_il *37 114 0.7%
func_breakable *25 69 0.4%
func_conveyor *20 69 0.4%
func_conveyor *21 69 0.4%
func_breakable *22 69 0.4%
func_breakable *23 69 0.4%
jet1
, jet2
and tonk
are hogging tons of clipnodes! Aren't those just cinematic props you see outside the window?
If you check in-game, you'll see that not only are those entities unreachable, but they have collision disabled anyway. You can walk right through the tonk
and all the other vehicles outside.
Also, why does that func_illusionary have any clipnodes? func_illusionary is supposed to be non-solid. I guess someone forgot to compile the map with -clipeconomy
.
I think it's safe to say we can delete all the clipnode hulls for those entities. They're useless!
copy echoes14b.bsp echoes14b_temp.bsp
bspguy noclip echoes14b_temp -model 39
bspguy noclip echoes14b_temp -model 17
bspguy noclip echoes14b_temp -model 42
bspguy noclip echoes14b_temp -model 37
bspguy merge test.bsp -maps "echoes14_temp, echoes14b_temp"
Data Type Current / Max Fullness
------------ ------------------- --------
...
clipnodes 28615 / 32767 87.3%
...
Much better. Note that the -optimize
command would have deleted these hulls automatically because they're marked as non-solid, but manual work will be needed to delete hulls for things that are unreachable.
In this scenario, you've found a model that uses up a lot of clipnodes, but you can't just delete its hulls because the entity needs collision to work properly. Your last available option now is to simplify the collision data for that model.
Here are the models in echoes01.bsp sorted by clipnode usage:
Classname Targetname Model Clipnodes Usage
------------------------- ------------------------- ----- ---------- --------
worldspawn *0 11326 76.8%
func_wall_toggle sci_cart *53 199 1.3%
func_wall_toggle sci_cart *54 170 1.2%
func_wall quake1_light1 *49 156 1.1%
func_wall *75 124 0.8%
func_wall quake1_light2 *48 86 0.6%
func_wall quake2_light2 *42 72 0.5%
func_door toilet1 *61 66 0.4%
func_door_rotating exit_door2 *27 60 0.4%
func_door_rotating exit_door1 *25 58 0.4%
The sci_cart
entities are found in a hallway, partially blocking the path. The clipnode hulls for these entities shouldn't be deleted because players can interact with them, but the hulls are way more complicated than they need to be. You could replace the collision hulls in these models with a simple box and no one would know the difference. So, that's just what we'll do.
bspguy simplify echoes01 -model 53
Simplifying HULL 1, 2, and 3 in model 53:
Deleted 43 planes
Deleted 181 clipnodes
Currently only axis-aligned bounding boxes are supported right now. This means that objects that are angled and/or not box-shaped won't work well with this.
Other limits you are likely to hit are nodes
, vertexes
, and marksurfaces
. These all have the same culprit - too many polygons and/or complicated structures in the map.
Deleting hull 0 in a model reduces all types of data usage except clipnodes, but be warned, the game will crash if you delete hull 0 in these scenarios:
- The entity is visible = game crash the first time you shoot
- The entity is solid = game crash when you stand on it
The -optimize
flag will automatically delete hull 0 for models that don't appear to need them, so try that first. If -optimize
causes instability or collision problems, use the -v
option with -optimize
to get a list of all the models that had their hulls deleted. Then you can selectively delete hulls from the models in that list.
An example of an entity that doesn't need hull 0 would be func_tankcontrols
. This is because it's invisible and doesn't interact with point-sized entities or bullets. A more complex example would be trigger_once
. While it's an invisible entity, it can be triggered by point-size entities if the "Everything else" flag is checked. If that flag is checked (or if that flag is ever added during gameplay), then deleting hull 0 might break the map.
If you know an entity can function properly without hull 0, then delete it with the noclip
command. For example, to delete hull 0 from model 1 in test.bsp:
bspguy noclip test.bsp -model 1 -hull 0
Sometimes, models are duplicated and used all over the map (e.g. picard coins in keen halloween). Duplicate models can be deleted and the remaining model can be used in multiple entities. Be aware that decals and lighting will be duplicated on entities that use the same model, but that's probably a small price to pay for the reduction in vertexes/nodes/etc.
Duplicate models can be deleted with the delete
command. For example, if models *1
and *2
were duplicates, you could run bspguy delete test.bsp -model 2
and then update the model
key in the entities that used *2
. You might also have to update the origin of the entity.
Bspguy comes with map scripts that are required for playing merged maps. Install the bspguy scripts by extracting the scripts
folder in the bspguy archive to svencoop_addon
.
When a map is merged, an .ent
file is generated along with the merged map. The .ent
file needs to be copied to scripts/maps/bspguy/maps/
. The script uses that file to create/delete entities as new map sections are entered. This file needs to be regenerated whenever the map entities change. That can be done with with ripent: ripent -export mapname
Lastly, add the following line to the map CFG for your merged map. This loads the map script.
map_script bspguy/v1/map
If your merged map already has its own map script, then you'll need to instead call the bspguy MapInit
/MapActivate
methods from within that script. For example, if your map uses the func_vehicle_custom script, then this is what the edited version should look like:
#include "func_vehicle_custom"
#include "bspguy/v1/bspguy"
void MapInit()
{
VehicleMapInit( true, true );
bspguy::MapInit();
}
void MapActivate()
{
bspguy::MapActivate();
}
Note: the scripts are in versioned folders. v1
is the latest version at the time of this writing. Use the latest version.
If you really don't want to use map scripts, you can use the -noscript
option with bspguy. Just be aware that maps with lots of ents may be laggy if you do this. There might also be more issues you'll have to fix with ripent (e.g. entities that had their classes changed to something less configurable).
Some entities are always edited so that levels can be played one after another (e.g. trigger_changelevel
-> trigger_once
). If you want to do all the ripent changes yourself, then add the -noripent
option as well. This will merge maps without touching any entity logic.
The bspguy script creates 3 trigger_script entities. These control entity loading and cleanup
- Target:
bspguy_mapchange
- loads entities in the next map section
- The section name is read from a custom keyvalue in the !caller (
$s_bspguy_next_map
)
- The section name is read from a custom keyvalue in the !caller (
- respawns all players in the next level
- deletes entities in the current level
- The section name is read from a custom keyvalue in the !caller (
$s_bspguy_map_source
)
- The section name is read from a custom keyvalue in the !caller (
- loads entities in the next map section
- Target:
bspguy_mapload
- loads entities in a map section.
- The section name is read from a custom keyvalue in the !caller (
$s_bspguy_next_map
)
- The section name is read from a custom keyvalue in the !caller (
- loads entities in a map section.
- Target:
bspguy_mapclean
- deletes entities in a map section
- The section name is read from a custom keyvalue in the !caller (
$s_bspguy_map_source
)
- The section name is read from a custom keyvalue in the !caller (
- deletes entities in a map section
You don't need to call these manually unless you're setting up seamless transitions. If you mix seamless transitions with the default ones, then you might want to call bspguy_mapclean
manually for maps that were part of a seamless section.
Repeat calls to mapload/mapclean/mapchange are ignored, if the same maps are requested.
Type bspguy
in console for debug commands.
To quickly test each map section, use the bspguy mapchange
command. In addition to skipping sections, this can also restart the current section or go back to previous ones.