Amulet Core

repository·2.0·Indexed 42 days ago

https://github.com/amulet-team/amulet-core

A Python 3 library for reading and writing various Minecraft save formats. It serves as the primary world editing engine for the Amulet Map Editor, utilizing a universal format to provide consistent editing and world conversion across different platforms and game versions.

Tokens
16.2K
Snippets
55
Records
97
Agent score
53%

What's inside Amulet Core

  1. How UnboundedPartial3DArray works

    2.0

    The UnboundedPartial3DArray is designed to solve the problem of storing 3D data (like blocks or biomes) with an unlimited vertical height without consuming infinite memory.

    Instead of one massive array, it uses a sparse approach: it stores a dictionary of numpy.ndarray objects, where each array represents a 16x16x16 volume called a sub-chunk (or section).

    Key concepts:

    • Sub-chunk/Section: A 16x16x16 block volume.
    • Sub-chunk coordinate: The vertical (y) index of a sub-chunk within a chunk. This is distinct from the raw block y-coordinate.
    • Unbounded vs Bounded: UnboundedPartial3DArray has no vertical limit. When you access a slice of it using numpy-like syntax, it returns a BoundedPartial3DArray, which represents a finite volume but remains linked to the original data. Modifying the bounded view will modify the original unbounded array.
  2. How the block and biome registries work

    2.0

    In Amulet Core, block and biome data is stored as numerical values in arrays to optimize space. The amulet.api.registry package provides a two-way mapping system that allows you to convert these numerical values back into meaningful objects or strings, and vice versa.

    • Numerical to Object/String: Converts an integer from the data arrays into an amulet.api.block.Block object or a biome string.
    • Object/String to Numerical: Converts a Block or biome string into the integer value required before adding it to the underlying data arrays.

    These registries are managed via the palettes stored in the level and chunks, specifically through the block_palette and biome_palette attributes of a BaseLevel object.

  3. Interpret Code Policy docstrings

    2.0

    Function docstrings use specific patterns to indicate locking requirements. These patterns are categorized by their Scope (when the lock applies) and their Requirement (whether the lock is mandatory).

    Scopes

    • Call:: The policy applies from the moment the function is called until it returns.
    • Return:: The policy applies from the moment the function is called until the returned value is no longer in use. (This is the default if no scope is specified).

    Requirement Types

    • Thread safe: The code can be executed from multiple threads without any external locking.
    • lock required: The caller must acquire the associated lock in a compatible mode for the defined scope to prevent state corruption.
    • lock optional: The caller may acquire a lock. This is used when state won't be corrupted by other threads, but might become outdated if other threads mutate it.

    Common Lock Shorthands

    • Unique lock (required|optional): Equivalent to ReadWrite:Unique.
    • Shared lock (required|optional): Equivalent to Read:SharedReadOnly.
  4. Choosing the correct Level class: World vs Structure

    2.0

    Amulet provides different level classes depending on the type of data you are interacting with:

    • World: Use this class when you are editing full world data.
    • Structure: Use this class when you are editing specific structure files (e.g., .schematic, .mcstructure, .schem).
    • ImmutableStructure: Use this class when you need to extract a section of a world without modifying the original source (for example, when performing a copy operation). This is typically obtained via the BaseLevel.extract_structure method.
  5. Understanding Level classes and the Universal Format

    2.0

    In Amulet, Level classes act as containers for all data related to a specific world or structure, including chunk data and history management for reverting changes.

    All data within these classes is stored in a universal format. This is a consistent internal representation that all data is converted to, regardless of its original source (e.g., different game versions or platforms). This abstraction allows for:

    • Consistent Editing: You can edit data from different platforms using the same logic.
    • World Conversion: The universal format can be converted out to any supported format, enabling seamless transitions between different world types.
  6. VersionNumber types

    2.0

    The translation system provides three distinct ways to represent version numbers to ensure compatibility across different data formats:

    • VersionNumberInt: Represents a version number as a single integer.
    • VersionNumberTuple: Represents a version number as a tuple of integers (e.g., (1, 2, 3)).
    • VersionNumberAny: A flexible type that can represent a version number in multiple formats.
  7. Understanding the Universal Block Format

    2.0

    Amulet uses a custom universal format to represent block states. This format is designed as a superset of all block formats from every platform and game version, ensuring no data loss during conversion.

    Critical Warning for Developers

    Do not hard-code data in the universal format. The universal format is subject to change as new game versions are released to support new features. Hard-coding these values will cause your code to break when Amulet is updated.

    To safely manipulate block data, follow this pattern:

    1. Use the translator to convert the universal representation into a specific platform and game version format.
    2. Edit the data in that specific, historical game format (which is stable and safe to hard-code).
    3. Convert the data back to the universal format.
    4. Write the universal format data back to the chunk.
  8. Understand the Amulet block state model

    2.0

    Amulet uses the modern block state system rather than the legacy numerical ID system used by older editors like MCEdit.

    In the modern format, chunks contain an array of integers that serve as indexes into a block palette (a list of block states) stored within the chunk. A block state consists of:

    1. A namespaced name (e.g., minecraft:spruce_leaves).
    2. A dictionary of properties (e.g., distance, persistent).

    This approach allows for arbitrary integer indexing and avoids the issues caused by modded clients remapping numerical IDs, as the specific identity and properties of a block are explicitly defined in the palette.

    {
        "Name": "minecraft:spruce_leaves",
        "Properties": {
            "distance": "1",
            "persistent": "false"
        }
    }
  9. How blocks and block palettes work in Amulet

    2.0

    In Amulet, block data is managed through a two-way mapping between integer indices and Block objects.

    1. Blocks Array: Chunks store block data as an array of arbitrary integer values. This array is accessed via the Chunk.blocks property and is an instance of the Blocks class.
    2. Block Palette: This is a mapping that links the integer indices in the blocks array to specific Block objects. The palette is managed by the BlockManager class and is accessible via BaseLevel.block_palette and Chunk.block_palette.

    Important Note on Indices: Because new block states are registered with the palette as they are discovered, block indices are not permanent and can change every time a world is loaded. Never rely on hard-coded integer indices for specific blocks.

    # Conceptual relationship
    # Chunk.blocks -> Array of integers (indices)
    # Chunk.block_palette -> Mapping (Index <-> Block object)