VanillaReimplementation

repository·master·Indexed 41 days ago

https://github.com/minestom/vanillareimplementation

A project providing a limited set of vanilla Minecraft features for use with Minestom. It allows developers to extend Minestom server functionality with vanilla-like features, including the registration of commands such as /gamemode, /difficulty, /me, and /stop, as well as support for vanilla entities, instances, and a comprehensive recipe system for crafting, cooking, and smithing.

Tokens
11.1K
Snippets
36
Records
64
Agent score
97%

What's inside VanillaReimplementation

  1. Initialize VanillaReimplementation in your server

    master

    Once you have added the required modules to your classpath, you can hook VanillaReimplementation into your server startup by calling VanillaReimplementation.hook() and passing the result of MinecraftServer.process().

    VanillaReimplementation vri = VanillaReimplementation.hook(MinecraftServer.process());
  2. Modify ItemStack properties with LootFunctions

    master

    In the Minestom vanilla implementation, LootFunction objects are used to modify ItemStack results within a loot table. Each function implements an apply(@NotNull ItemStack input, @NotNull LootContext context) method. Most functions also support a list of LootPredicate objects called conditions; if these predicates are not met within the given LootContext, the function returns the original input without making changes.

    Common patterns for LootFunction include:

    • Setting components: Modifying data components like enchantments, potion effects, or custom data.
    • Modifying quantities: Using SetCount to change the stack size.
    • Sequencing: Using Sequence to apply multiple functions in order.
  3. Implement complex logic with AllOf and AnyOf

    master

    You can combine multiple LootPredicate instances to create complex conditional logic:

    • AllOf: Requires every predicate in the terms list to return true.
    • AnyOf: Requires at least one predicate in the terms list to return true. If the list is empty, it returns false.
    • Inverted: Wraps a single predicate and returns the opposite of its result.
  4. Define crafting recipes using the Recipe interface

    master

    The Recipe interface is the base type for all recipe implementations in the vanilla implementation. It is a sealed interface that includes various recipe types such as crafting, smelting, smithing, and special recipes. Every recipe must implement a codec() method to provide its StructCodec for serialization/deserialization.

    Key sub-types include:

    • Crafting: Includes Shaped, Shapeless, and Transmute recipes.
    • Cooking: Includes Smelting, Smoking, Blasting, and Campfire recipes.
    • Smithing: Includes Transform and Trim recipes.
    • Stonecutting: A recipe for the stonecutter.
    • Special Recipes: Various specialized recipes like SpecialArmorDye, SpecialFireworkRocket, etc.
  5. Understand LootEntry and its types

    master

    A LootEntry is a fundamental building block of a loot table that generates potential loot choices. When a loot table is processed, an entry is evaluated against a LootContext to determine which Choice objects are available for selection.

    There are several types of LootEntry implementations available via the loot_entries registry:

    • item: A single item defined by a Material.
    • group: A collection of entries that are all evaluated; their choices are merged into a single list.
    • sequence: A collection of entries evaluated in order. If any entry in the sequence fails to provide choices, the sequence stops.
    • alternatives: Evaluates entries in order and returns the choices from the first entry that satisfies its predicates.
    • tag: Expands a registry tag (e.g., a tag of Materials) into multiple individual item choices.
    • loot_table: References another loot table (either by Key or directly).
    • empty: Produces no items.
    • dynamic: Generates items dynamically based on the LootContext (e.g., extracting contents from a container block).

    Each entry can be associated with LootPredicates (conditions that must be met) and LootFunctions (modifications applied to the generated items).

  6. LootNumber implementations

    master

    The LootNumber interface provides several implementations for different numeric generation strategies:

    • Constant(Double value): Always returns the same fixed value.
    • Uniform(LootNumber min, LootNumber max): Returns a value uniformly distributed between the min and max values. Uses LootContext.RANDOM for generation.
    • Binomial(LootNumber trials, LootNumber probability): Returns the number of successes in a sequence of trials experiments, each with a probability of success. Uses LootContext.RANDOM.
    • EnchantmentLevel(LevelBasedValue amount): Calculates a value based on the enchantment level found in the LootContext.ENCHANTMENT_LEVEL.
    • Score(LootScore target, String objective, double scale): Retrieves a score from a target (defined by LootScore) for a specific objective and multiplies it by a scale.
    • Storage(Key storage, NBTPath path): Attempts to retrieve a numeric value from NBT storage at a specific path.
  7. Implement LootNBT via Context or Storage

    master

    There are two primary implementations of LootNBT provided in the vanilla implementation:

    1. Context: Uses a Target to extract NBT from the environment.
      • block_entity: Returns the NBT of a block, augmented with its x, y, z coordinates and its id.
      • Entity: Returns NBT from a specific entity type (implementation of entity serialization is currently a TODO).
    2. Storage: Intended to pull NBT from a specific Key source (implementation of command storage is currently a TODO).

    Both implementations use StructCodec for serialization.

    // Context implementation example (BlockEntity target)
    // When target is 'block_entity', the resulting NBT includes:
    // { "x": int, "y": int, "z": int, "id": string, ...original_block_nbt }
  8. How LootEntry weight and quality work

    master

    Loot entries can use a Standard weight calculation to determine the likelihood of a choice being selected. The final weight is calculated using both a base weight and a quality value, which is influenced by the player's luck.

    Formula: finalWeight = max(1, floor(weight + (quality * luck)))

    Where luck is the value retrieved from the LootContext using the LootContext.LUCK key.

    • weight(): The base weight of the choice (minimum 1).
    • quality(): A multiplier for the context's luck value.

    This mechanism allows for 'luck-based' loot where higher quality entries become more probable as the context's luck increases.

  9. Interpolated and Cached Density Functions

    master

    To optimize world generation, several wrapping functions can be used to cache or smooth density values:

    • Interpolated(DensityFunction argument): Wraps a function and performs linear interpolation (lerp) between 4x4x4 block corners to smooth the output.
    • Cache2D(DensityFunction argument): Computes the input density only once per horizontal (X/Z) position, caching the result in a DoubleStorage.
    • CacheOnce(DensityFunction argument): Caches the last computed value, only re-computing if the block coordinates change.
    • FlatCache(DensityFunction argument): Caches the value for the current 4x4x4 'quart' area.
  10. Launch a vanilla-like server

    master

    To run a standalone server with vanilla features, you can use the VanillaServer.main entrypoint. This initializes the Minestom MinecraftServer, hooks the VanillaReimplementation (VRI) into the server process, and starts the server on a specified address and port.

    Note that the implementation uses VanillaReimplementation.hook(MinecraftServer.process()) to integrate vanilla logic into the core Minestom lifecycle.

    public static void main(String[] args) {
        // Use the static server process
        MinecraftServer server = MinecraftServer.init();
    
        // Use SETUP logging level since this is a standalone server.
        Loading.level(Level.SETUP);
        Logger.info("Setting up vri...");
        VanillaReimplementation vri = VanillaReimplementation.hook(MinecraftServer.process());
    
        VanillaServer vanillaServer = new VanillaServer(server, vri, args);
        Logger.info("Vanilla Reimplementation (%s) is setup.", MinecraftServer.VERSION_NAME);
    
        vanillaServer.start("0.0.0.0", 25565);
    }