Modding:Minidimension: Difference between revisions

From Vintage Story Wiki
(Document minidimensions)
 
(No difference)

Latest revision as of 02:58, 26 June 2024

This page was last verified for Vintage Story version 1.19.8.

Minidimensions contain chunks of blocks that are rendered along with the chunks of the normal world. By setting the CurrentPosition property on the minidimension, one can change the location at which the minidimension is rendered on top of the normal world. In addition to translating the minidimension relative to the normal world, the CurrentPosition property also allows arbitrary rotations.

The base game use a minidimension in the world edit import tool to show a semitransparent preview of what the imported blocks would look like at the cursor position.

The other intended use case for minidimensions is to function as a vehicle that the player can build with blocks. The base game has a demo of this which invoked in creative mode with the /we testlaunch command. However, currently numerous necessary features are missing in order for that use case to be anything more than a tech demo. Some of these limitations are described in the movchunks public notes documentation that was included in the 1.19.0 release notes. More limitations are described in this wiki page. Theoretically these limitations could be overcome with enough Harmony patches.

Dimension identifiers

There are 3 different dimension related identifiers. The dimension id differentiates full dimensions. Currently the only used values are 0 for the normal world and 1 for the minidimension container. Constants for those value are stored in the Vintagestory.API.Config.Dimensions enum. Dimension ids 2 through 9 are reserved for future vanilla use. Dimension ids 10 through 1023 are available for mods, but the game does not have any functions to create those dimensions.

The minidimension container is subdivided into minidimensions. These minidimesions are identified by their subdimension id. There are functions for creating minidimensions.

Minidimensions are also identified by an index in the ServerMain.LoadedMiniDimensions dictionary. By convention, the minidimension index and subdimension id are set to the same value.

ChunkIndex3D

Chunks are cubes with a length of 32 blocks. Every valid BlockPos must point to a block inside of a valid chunk position. Internally the dictionary of loaded chunks is indexed by a long. That long is called a ChunkIndex3D, which contains the X, Y, Z, dimension, and sometimes subdimension coordinates of the chunk. The way in which these coordinates are packed into the long helps explain the (very generous) map size limits that were introduced when minidimensions were added.

ChunkIndex3D fields in most significant bit first order
reserved dimension guard chunkY chunkZ chunkX
2 bits 10 bits 1 bit 9 bits 21 bits 21 bits

ChunkIndex3D field ranges:

  • reserved - always 0
  • guard - always 0. This is used to detect if some mod accidentally created a BlockPos with a Y value above the limit.
  • dimension - [0, 1024). Currently only 0 (normal world) and 1 (minidimension parent) are used.
  • chunkX - [0, 2097152)
  • chunkY - [0, 512)
  • chunkZ - [0, 2097152)

Since a chunk is 32 by 32 by 32, the limits on the chunk coordinates translate into these block ranges for the normal world.

  • blockX - [0, 67108864)
  • blockY - [0, 16384)
  • blockZ - [0, 67108864)

However, the minidimension container is further subdivided into minidimensions. This extra dimensional data is taken from the chunkX and chunkZ fields.

Minidimension ChunkIndex3D fields in most significant bit first order
reserved dimension guard chunkY subdimension upper chunkZ subdimension lower chunkX
2 bits 10 bits 1 bit 9 bits 12 bits 9 bits 12 bits 9 bits

And these are the field ranges for a minidimension ChunkIndex3D:

  • reserved - always 0
  • guard - always 0
  • dimension - always 1 for the minidimension container
  • subdimension - [0, 16777216)
  • minidimension chunkX - [0, 512)
  • minidimension chunkY - [0, 512)
  • minidimension chunkZ - [0, 512)

The AdjustPosForSubDimension function packs the subdimension id into the upper bits X and Z bits of a BlockPos. It treats (0, 0, 0) as the center of a minidimension, whereas for the normal world, (0, 0, 0) is at the edge of the map. So the field ranges below take this translation into account.

  • minidimension blockX - [-8192, 8192)
  • minidimension blockY - [-8192, 8192)
  • minidimension blockZ - [-8192, 8192)

Life of a minidimension

Server side: Create the minidimension

Typically one calls CreateMiniDimension on the block accessor to create the IMiniDimension object. The function takes a Vec3d representing where in the normal world to anchor the center of the minidimension. If the center location not known yet, just use new Vec3d(). The center location can be easily adjusted later by setting the CurrentPos field on the IMiniDimension.

Example:

  IMiniDimension dim = sapi.World.BlockAccessor.CreateMiniDimension(new Vec3d());

Internally, CreateMiniDimension constructs and returns a BlockAccessorMovable. BlockAccessorMovable is not part of the API module, but the class is still public and can be accessed from VintagestoryLib.dll. Many of the methods in BlockAccessorMovable are virtual and public or protected. It is possible to customize the minidimension behavior by creating a new class that inherits from BlockAccessorMovable and then overriding methods. In this case, the custom class would be constructed directly instead of calling CreateMiniDimension.

Server side: register the minidimension with an index

On the server, a IMiniDimension object can be registered to a new minidimension index with LoadMiniDimension. The saved game contains an integer for the highest minidimesion index ever used. That function returns the next value and increments the integer.

The vanilla game does not contain any functions to free an minidimension index after it is allocated, and the saved file does not contain a free list of unused minidimension indices. Mods are encouraged to either: 1. Allocate a fixed number of minidimensions, and store those allocated indices inside of the save game mod data. 2. Maintain their own mod specific free list of minidimension indices. This free list should be stored inside the save game mod data.

On the server side, to recycle a minidimension index, either the existing IMiniDimension object can be cleared and reused, or a new IMiniDimension object can be registered to the existing index with SetMiniDimension (instead of calling IServerAPI.LoadMiniDimension).

To maintain the subdimension id convention, after the registering the minidimension to an index, the subDimensionId property of the IMiniDimension should be set to its index. IMiniDimension also has a SetSubDimensionId method. This method is redundant, and calling it just sets the property.

Client side: create the minidimension

The minidimension system only transfers chunk contents from the server to the client. It is up to the mod to transfer over any other settings for the minidimension. Notably, the CurrentPos must be set on both the server (it is set by CreateMiniDimension) and client side. The minidimension system will not automatically transfer the value set on the server to the client.

The GetOrCreateDimension function on the IClientWorldAccessor is used to create a default initialized minidimension. The CurrentPos should always be set on the returned IMiniDimension, because GetOrCreateDimension only sets it if the dimension was newly created, and the IMiniDimension may have been default initialized earlier.

If the server starts modifying blocks in the minidimension before the mod on the client explicitly creates the minidimension, then the minidimension will be automatically created on the client with default values. The default initialized minidimension will not have CurrentPos property set to a valid value. It is possible to fill in the chunks on the server side first, then fix the CurrentPos property on the client. However, if the minidimension is to be rendered as the semitransparent preview minidimension, then that must be set before the chunk contents are transferred. Because the semitransparency is a property of the tessellated chunks, and the chunks are tessellated shortly after they are transferred. Marking the dimension as the preview dimension will not cause the chunks to be retessellated.

The preview dimension is set by passing the subdimensionid to SetBlocksPreviewDimension. There can only be one preview dimension per client (each player could theoretically have a different one). The preview dimension can be disabled by passing -1. On the client side, one can check whether a given IMiniDimension is the current preview dimension through the TrackSelection property. The preview dimension gets two special features: 1. Its blocks are rendered semitransparently. Specifically its blocks are rendered in the transparency pass instead of the opaque pass, and transparency is added to the block textures. 2. The dimension center follows around the cursor. Specifically, the current block selection for the dimension center instead of the CurrentPos property.

Just like the server, GetOrCreateDimension uses BlockAccessorMovable as its implementation of IMiniDimension. It is possible to use a custom IMiniDimension implementation on the client by inheriting from BlockAccessorMovable and overriding fields. In this case, register the dimension directly adding it to the Dimensions dictionary in IClientWorldAccessor. Use the subdimension id as the index.

One reason to create a custom IMiniDimension is to give it the transparency of the preview dimension without the cursor tracking. To disable the cursor tracking, override the GetRenderOffset method. There can still only be one transparent minidimension per client, because most of the transparency is handled by TesselatedChunkPart.AddToPools, which calls the static method BlockAccessorMovable.IsTransparent, which only looks at the subdimension id.

Server side: fill the minidimension

IMiniDimension inherits from IBlockAccessor. So it is the preferred object for setting blocks. With the correct BlockPos, it is also possible to replace blocks inside the minidimension chunks through the default block accessor (the one typically used for the normal world). The default block accessor does not always work for setting blocks the first time, because any chunks that it implicitly creates will not be registered with the minidimension, which means they will not be anchored into the normal world based on the CurrentPos field, and the chunks will not be sent to nearby players.

To calculate a BlockPos inside the minidimension, first create a BlockPos relative to (0, 0, 0), then pass it to IMiniDimension.AdjustPosForSubDimension. That function will set the dimension index, and add the subdimension id bits to the X and Z coordinates.

It is possible to set blocks through the IMiniDimension using unadjusted coordinates (such as (0, 0, 0)). BlockAccessorMovable will still find the chunks in the correct subdimension id. However, the unadjusted coordinates will be given to functions such as Block.OnBlockPlaced. So the block and its behaviors will not be able to see the block in the world or its neighbors.

Clear the minidimension

It is not possible to unregister a minidimension on the server side, without resorting to reflection or harmony patches. On the client side it is possible but not recommended to unregister a minidimension. Instead, call ClearChunks on both sides, and optionally UnloadUnusedServerChunks on the server side.

Limitations

  • Minidimensions cannot hold entities
  • The following blocks are known to render improperly in minidimensions. An easy way to test whether a block renders correctly in the minidimension is to use worldedit to copy a schematic with the block to the clipboard, then use the import tool to preview it.
    • Doors - does not render at all
    • Wooden axle - does not render at all. The custom renderer for the rotating shaft does not take the minidimension render offset into account. The axle stands are not rendered because BEBehaviorMPBase uses the dimension unaware accessors, so it cannot see the neighboring blocks in the minidimension and tell which direction the stands should be anchored to.
  • Minidimension chunks are only sent to nearby players when the chunks are modified. The chunks are not sent when a new player joins, when a player walks to a minidimension that was previously out of view range, or when a previously out of view range minidimension is moved to a player by setting CurrentPos.
    • Except for server selection tracking minidimensions, whose chunks are always sent to all logged in players regardless of where they are. A minidimension is marked as server selection tracking passing its subdimension id to SetSelectionTrackingSubId_Server. Passing any other value to the function just reverts the minidimension to normal.
  • BlockAccessorStrict will not allow reading blocks outside of the main dimension. Most accessors are relaxed, but a strict accessor can be created via the IWorldAccessor.GetBlockAccessor method with strict=true.
    • Specifically GetBlock calls GetBlockId with pos.InternalY, and BlockAccessorStrict.GetBlockId fails because posY is greater than worldmap.MapSizeY, because the dimension is packed into the upper bits of posY.
  • The dictionary of registered minidimensions is iterated on every server tick. So even though the ChunkIndex3D supports 16777216 subdimensions, registering 1000 or so will add server tick latency.


More limitations are described in the movchunks public notes.

Icon Sign.png

Wondering where some links have gone?
The modding navbox is going through some changes! Check out Navigation Box Updates for more info and help finding specific pages.

Modding
Modding Introduction Getting Started Theme Pack
Content Modding Content Mods Developing a Content Mod Basic Tutorials Intermediate Tutorials Advanced Tutorials Content Mod Concepts
Code Modding Code Mods Setting up your Development Environment
Property Overview ItemEntityBlockBlock BehaviorsBlock ClassesBlock EntitiesBlock Entity BehaviorsWorld properties
Workflows & Infrastructure Modding Efficiency TipsMod-engine compatibilityMod ExtensibilityVS Engine
Additional Resources Community Resources Modding API Updates Programming Languages List of server commandsList of client commandsClient startup parametersServer startup parameters
Example ModsAPI DocsGitHub Repository