mirror of
https://github.com/GeyserMC/Geyser.git
synced 2024-08-14 23:57:35 +00:00
* Super cursed custom skulls custom block * Rename some stuff * Attempt to clean up some code * Remove skull translation events and define custom blocks for custom skulls Clean up skull block translation a bit * Auto generate skull resource pack Change `davchoo` to `geyser` in geometry * Add config options for custom blocks and custom skull blocks * Fix formatting and names for player skulls * Use block states more efficiently for custom skulls 21 block states vs 48 block states * Clean up custom block api a bit * Apply some suggestions from Camotoy * Move custom skull config stuff to its own file Custom skulls can now be added by username, uuid, and textures Move skull nbt stuff from requestTexturesFromUsername to SkullBlockEntityTranslator Add requestTexturesFromUUID * Update custom block nbt for v534 * Disable collision box & selection box when box is empty Fix incorrect collision names used in CustomBlockComponentsBuilder * Add custom block stuff to provider registry loader * More API changes Convert CustomBlockPermutation into a record Change materialInstances in CustomBlockComponents Builder to materialInstance Reuse box components in CustomSkull * Convert skull floor geometries into a template Should be easier to modify in needed in the future. * Crop and reorder skull textures to eliminate unused space Should reduce memory & storage usage for Bedrock clients * Revert "Crop and reorder skull textures to eliminate unused space" This reverts commit15fd5353e1
. * Use identifier from CustomBlockData in SkullResourcePackManager * Fix isIncorrectHeldItem check for custom skull blocks Add defaultBlockState to CustomBlockData * Fix adding duplicate block states for custom blocks with 0 properties Remove defaultBlockState CustomBlockState field from GeyserCustomBlockData since it creates a circular reference * Add basis for overriding Bedrock block states Fix missing providers when used in GeyserDefineCustomBlocksEvent * Fix custom blocks in 1.19.50 * Decouple mappings from items * Decouple mappings from items * Null check * Move to CustomBlockRegistryPopulator * Remove name_hash from blocksTag/vanillaBlockStates Fixes creative inventory contents with custom blocks registered * Limit Bedrock versions to 1.19.40+ Custom blocks were released in 1.19.40 * Un-revert Crop and reorder skull textures to eliminate unused space Should reduce memory & storage usage for Bedrock clients Bug with top face flipping + per-face uv's was fixed in 1.19.40+ https://bugs.mojang.com/browse/MCPE-160073 Geometry is still offset by 0.5 to prevent lighting bugs * Add validation custom block components and s/lightFilter/lightDampening/ Also validate custom block names * Add display name component and add toggle for client block placing The display name component allows blocks to use other locale keys. placeAir will prevent the client from placing the default block state. * Begin parsing block mappings (still much to do!) * CustomBlockMapping stores block w/ all states * Mappings almost :/ * Ok now they work at least * Read most mapping components * Block mappings mostly done * Translate block item * Add docs for custom blocks * Add tags * More docs * Accidentally added name comp. * Fix collide box and warn for >16 props * add registerBlockItemOverride event + refactor * Inventory overrides for multistate bedrock blocks * Implement all remaining block components * Minor cleanup and javadocs * Update custom skull config example * Address @Camotoy's review Signed-off-by: Joshua Castle <26531652+Kas-tle@users.noreply.github.com> * Fix light_emission and light_dampening components * Remove redundant populate method and remove BLOCKS_JSON after last use * Fix inventories with block state overrides not opening * API event for skull blocks & let register via URL Signed-off-by: Joshua Castle <26531652+Kas-tle@users.noreply.github.com> * Use skin hash instead of URL Signed-off-by: Joshua Castle <26531652+Kas-tle@users.noreply.github.com> * Address @davchoo's review Signed-off-by: Joshua Castle <26531652+Kas-tle@users.noreply.github.com> * Rework MappingsReader_v1 to avoid passing maps around * Treat all properties as string properties There isn't a real need to check for boolean and int properties * Fix block registry scan in MappingsReader * Skin hashes can have less than 64 characters? * Include entry when logging exceptions from block mappings * Submodule Signed-off-by: Joshua Castle <26531652+Kas-tle@users.noreply.github.com> * Fix block break speeds thanks to @Camotoy Signed-off-by: Joshua Castle <26531652+Kas-tle@users.noreply.github.com> * Temporarily fix build on eclipse so I may work... Signed-off-by: Joshua Castle <26531652+Kas-tle@users.noreply.github.com> * Custom tool breakspeed by server; Closes #3348 Signed-off-by: Joshua Castle <26531652+Kas-tle@users.noreply.github.com> * Account for if custom skulls are added on 1st run Signed-off-by: Joshua Castle <26531652+Kas-tle@users.noreply.github.com> * Initial framework for extended collision boxes Signed-off-by: Joshua Castle <26531652+Kas-tle@users.noreply.github.com> * Add some notes for the extended collision box impl Signed-off-by: Joshua Castle <26531652+Kas-tle@users.noreply.github.com> * We have our extended collision registry Signed-off-by: Joshua Castle <26531652+Kas-tle@users.noreply.github.com> * Notes for me Signed-off-by: Joshua Castle <26531652+Kas-tle@users.noreply.github.com> * Extended collision boxes almost work Signed-off-by: Joshua Castle <26531652+Kas-tle@users.noreply.github.com> * Extended collision boxes actually work Signed-off-by: Joshua Castle <26531652+Kas-tle@users.noreply.github.com> * Consider all hitboxes in calculation Signed-off-by: Joshua Castle <26531652+Kas-tle@users.noreply.github.com> * X is mirrored... Signed-off-by: Joshua Castle <26531652+Kas-tle@users.noreply.github.com> * Extended collision boxes are much improved Signed-off-by: Joshua Castle <26531652+Kas-tle@users.noreply.github.com> * Upstream fallout Signed-off-by: Joshua Castle <26531652+Kas-tle@users.noreply.github.com> * Address @Redned235's review Signed-off-by: Joshua Castle <26531652+Kas-tle@users.noreply.github.com> * Oops my bad that makes no sense :) Signed-off-by: Joshua Castle <26531652+Kas-tle@users.noreply.github.com> * Ext collision box chunk translation optimization Signed-off-by: Joshua Castle <26531652+Kas-tle@users.noreply.github.com> * Trunc skinhash to 32 chars due to 80 char limit Signed-off-by: Joshua Castle <26531652+Kas-tle@users.noreply.github.com> * Use new transformation cmpnt vs legacy rotation Signed-off-by: Joshua Castle <26531652+Kas-tle@users.noreply.github.com> * keep arr null on get extcolstor Signed-off-by: Joshua Castle <26531652+Kas-tle@users.noreply.github.com> * Properly handle if extended collision box is below Signed-off-by: Joshua Castle <26531652+Kas-tle@users.noreply.github.com> * Less ugly (realized it can go here) Signed-off-by: Joshua Castle <26531652+Kas-tle@users.noreply.github.com> * Prevent 2x placement due to extended collision box Signed-off-by: Joshua Castle <26531652+Kas-tle@users.noreply.github.com> * Properly build on eclipse via indra Signed-off-by: Joshua Castle <26531652+Kas-tle@users.noreply.github.com> * Ensure enough bits in bedrockData for paletteIDs Signed-off-by: Joshua Castle <26531652+Kas-tle@users.noreply.github.com> * Fix not needed whitespace Signed-off-by: Joshua Castle <26531652+Kas-tle@users.noreply.github.com> * Update license headers to 2023 Signed-off-by: Joshua Castle <26531652+Kas-tle@users.noreply.github.com> * Use release indra over snapshot Signed-off-by: Joshua Castle <26531652+Kas-tle@users.noreply.github.com> * Revert "Update license headers to 2023" This reverts commitf750059e8e
. * Account for collisions in chunk section y0 layer Signed-off-by: Joshua Castle <26531652+Kas-tle@users.noreply.github.com> * Fix extended collision @ air section bottom Signed-off-by: Joshua Castle <26531652+Kas-tle@users.noreply.github.com> * Address @davchoo's review Signed-off-by: Joshua Castle <26531652+Kas-tle@users.noreply.github.com> * Address @rtm516's review Signed-off-by: Joshua Castle <26531652+Kas-tle@users.noreply.github.com> * More @rtm516's review Signed-off-by: Joshua Castle <26531652+Kas-tle@users.noreply.github.com> * Address @Camotoy's review Signed-off-by: Joshua Castle <26531652+Kas-tle@users.noreply.github.com> * Update javadocs Signed-off-by: Joshua Castle <26531652+Kas-tle@users.noreply.github.com> * Address @davchoo's review Signed-off-by: Joshua Castle <26531652+Kas-tle@users.noreply.github.com> * Lock extended collision to section Signed-off-by: Joshua Castle <26531652+Kas-tle@users.noreply.github.com> * Clear ext col even when air Signed-off-by: Joshua Castle <26531652+Kas-tle@users.noreply.github.com> * Let override vanilla items in creative inventory Signed-off-by: Joshua Castle <26531652+Kas-tle@users.noreply.github.com> * Avoid creating 12 HashSets for every overrided block state * Super minor nitpicks + Custom Skull NBT fix * Check custom skull is within Bedrock bounds Fixes NPE with custom skulls above y=320 or below y=-64 * Add static builder methods to match CustomItemData API * Upstream Signed-off-by: Joshua Castle <26531652+Kas-tle@users.noreply.github.com> * Initial API setup for modded blocks (no impl yet) Signed-off-by: Joshua Castle <26531652+Kas-tle@users.noreply.github.com> * More work on nonvanilla blocks (nonfunctional) Signed-off-by: Joshua Castle <26531652+Kas-tle@users.noreply.github.com> * Fix compile Signed-off-by: Joshua Castle <26531652+Kas-tle@users.noreply.github.com> * Update submodules Signed-off-by: Joshua Castle <26531652+Kas-tle@users.noreply.github.com> * Modded reg so far (not done) Signed-off-by: Joshua Castle <26531652+Kas-tle@users.noreply.github.com> * Add non-vanilla registration and fix a few bugs * Fixes for non-vanilla blocks * Remove import * CustomRegPop. go1st for now; must split for modded Signed-off-by: Joshua Castle <26531652+Kas-tle@users.noreply.github.com> * Address silent change to geo component for blocks Co-Authored-By: Unoqwy <pm@unoqwy.dev> Signed-off-by: Joshua Castle <26531652+Kas-tle@users.noreply.github.com> * Seperate bedrock, vanilla, & nonvanilla block reg Signed-off-by: Joshua Castle <26531652+Kas-tle@users.noreply.github.com> * Single event Signed-off-by: Joshua Castle <26531652+Kas-tle@users.noreply.github.com> * Impl MaterialInstance as builder per @Redned235 Signed-off-by: Joshua Castle <26531652+Kas-tle@users.noreply.github.com> * Added creative category enum & added some missing overrides (#7) * Add material instance to provider registry Signed-off-by: Joshua Castle <26531652+Kas-tle@users.noreply.github.com> * oops Signed-off-by: Joshua Castle <26531652+Kas-tle@users.noreply.github.com> * Fix case of correctBedrockIdentifier not found Signed-off-by: Joshua Castle <26531652+Kas-tle@users.noreply.github.com> * Fix docs Signed-off-by: Joshua Castle <26531652+Kas-tle@users.noreply.github.com> * Address @Camotoy's review Signed-off-by: Joshua Castle <26531652+Kas-tle@users.noreply.github.com> * Address review from @davchoo Signed-off-by: Joshua Castle <26531652+Kas-tle@users.noreply.github.com> * Set namespace of custom blocks vs ident direct Signed-off-by: Joshua Castle <26531652+Kas-tle@users.noreply.github.com> * Address review from @rtm516 Signed-off-by: Joshua Castle <26531652+Kas-tle@users.noreply.github.com> * One more Signed-off-by: Joshua Castle <26531652+Kas-tle@users.noreply.github.com> * Remove rogue space * Geo component as builder Signed-off-by: Joshua Castle <26531652+Kas-tle@users.noreply.github.com> * use super name Signed-off-by: Joshua Castle <26531652+Kas-tle@users.noreply.github.com> * Bump version Signed-off-by: Joshua Castle <26531652+Kas-tle@users.noreply.github.com> --------- Signed-off-by: Joshua Castle <26531652+Kas-tle@users.noreply.github.com> Signed-off-by: GitHub <noreply@github.com> Co-authored-by: davchoo <davchoo@users.noreply.github.com> Co-authored-by: davchoo <4722249+davchoo@users.noreply.github.com> Co-authored-by: Unoqwy <pm@unoqwy.dev> Co-authored-by: RednedEpic <redned235@gmail.com> Co-authored-by: ImDaBigBoss <67973871+ImDaBigBoss@users.noreply.github.com> Co-authored-by: rtm516 <rtm516@users.noreply.github.com>
517 lines
26 KiB
Java
517 lines
26 KiB
Java
/*
|
|
* Copyright (c) 2019-2022 GeyserMC. http://geysermc.org
|
|
*
|
|
* Permission is hereby granted, free of charge, to any person obtaining a copy
|
|
* of this software and associated documentation files (the "Software"), to deal
|
|
* in the Software without restriction, including without limitation the rights
|
|
* to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
|
* copies of the Software, and to permit persons to whom the Software is
|
|
* furnished to do so, subject to the following conditions:
|
|
*
|
|
* The above copyright notice and this permission notice shall be included in
|
|
* all copies or substantial portions of the Software.
|
|
*
|
|
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
|
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
|
* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
|
* AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
|
* LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
|
* OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
|
|
* THE SOFTWARE.
|
|
*
|
|
* @author GeyserMC
|
|
* @link https://github.com/GeyserMC/Geyser
|
|
*/
|
|
|
|
package org.geysermc.geyser.registry.populator;
|
|
|
|
import com.google.common.collect.Multimap;
|
|
import org.checkerframework.checker.nullness.qual.NonNull;
|
|
import org.cloudburstmc.nbt.NbtMap;
|
|
import org.cloudburstmc.nbt.NbtMapBuilder;
|
|
import org.cloudburstmc.nbt.NbtType;
|
|
import org.cloudburstmc.protocol.bedrock.data.definitions.ItemDefinition;
|
|
import org.cloudburstmc.protocol.bedrock.data.definitions.SimpleItemDefinition;
|
|
import org.cloudburstmc.protocol.bedrock.data.inventory.ComponentItemData;
|
|
import org.geysermc.geyser.GeyserImpl;
|
|
import org.geysermc.geyser.api.item.custom.CustomItemData;
|
|
import org.geysermc.geyser.api.item.custom.CustomRenderOffsets;
|
|
import org.geysermc.geyser.api.item.custom.NonVanillaCustomItemData;
|
|
import org.geysermc.geyser.api.util.TriState;
|
|
import org.geysermc.geyser.event.type.GeyserDefineCustomItemsEventImpl;
|
|
import org.geysermc.geyser.item.GeyserCustomMappingData;
|
|
import org.geysermc.geyser.item.Items;
|
|
import org.geysermc.geyser.item.components.WearableSlot;
|
|
import org.geysermc.geyser.item.type.Item;
|
|
import org.geysermc.geyser.registry.mappings.MappingsConfigReader;
|
|
import org.geysermc.geyser.registry.type.GeyserMappingItem;
|
|
import org.geysermc.geyser.registry.type.ItemMapping;
|
|
import org.geysermc.geyser.registry.type.NonVanillaItemRegistration;
|
|
|
|
import javax.annotation.Nullable;
|
|
import java.util.*;
|
|
|
|
public class CustomItemRegistryPopulator {
|
|
public static void populate(Map<String, GeyserMappingItem> items, Multimap<String, CustomItemData> customItems, List<NonVanillaCustomItemData> nonVanillaCustomItems) {
|
|
MappingsConfigReader mappingsConfigReader = new MappingsConfigReader();
|
|
// Load custom items from mappings files
|
|
mappingsConfigReader.loadItemMappingsFromJson((key, item) -> {
|
|
if (CustomItemRegistryPopulator.initialCheck(key, item, items)) {
|
|
customItems.get(key).add(item);
|
|
}
|
|
});
|
|
|
|
GeyserImpl.getInstance().eventBus().fire(new GeyserDefineCustomItemsEventImpl(customItems, nonVanillaCustomItems) {
|
|
@Override
|
|
public boolean register(@NonNull String identifier, @NonNull CustomItemData customItemData) {
|
|
if (CustomItemRegistryPopulator.initialCheck(identifier, customItemData, items)) {
|
|
customItems.get(identifier).add(customItemData);
|
|
return true;
|
|
}
|
|
return false;
|
|
}
|
|
|
|
@Override
|
|
public boolean register(@NonNull NonVanillaCustomItemData customItemData) {
|
|
if (customItemData.identifier().startsWith("minecraft:")) {
|
|
GeyserImpl.getInstance().getLogger().error("The custom item " + customItemData.identifier() +
|
|
" is attempting to masquerade as a vanilla Minecraft item!");
|
|
return false;
|
|
}
|
|
|
|
if (customItemData.javaId() < items.size()) {
|
|
// Attempting to overwrite an item that already exists in the protocol
|
|
GeyserImpl.getInstance().getLogger().error("The custom item " + customItemData.identifier() +
|
|
" is attempting to overwrite a vanilla Minecraft item!");
|
|
return false;
|
|
}
|
|
|
|
nonVanillaCustomItems.add(customItemData);
|
|
return true;
|
|
}
|
|
});
|
|
|
|
int customItemCount = customItems.size() + nonVanillaCustomItems.size();
|
|
if (customItemCount > 0) {
|
|
GeyserImpl.getInstance().getLogger().info("Registered " + customItemCount + " custom items");
|
|
}
|
|
}
|
|
|
|
public static GeyserCustomMappingData registerCustomItem(String customItemName, Item javaItem, GeyserMappingItem mapping, CustomItemData customItemData, int bedrockId) {
|
|
ItemDefinition itemDefinition = new SimpleItemDefinition(customItemName, bedrockId, true);
|
|
|
|
NbtMapBuilder builder = createComponentNbt(customItemData, javaItem, mapping, customItemName, bedrockId);
|
|
ComponentItemData componentItemData = new ComponentItemData(customItemName, builder.build());
|
|
|
|
return new GeyserCustomMappingData(componentItemData, itemDefinition, customItemName, bedrockId);
|
|
}
|
|
|
|
static boolean initialCheck(String identifier, CustomItemData item, Map<String, GeyserMappingItem> mappings) {
|
|
if (!mappings.containsKey(identifier)) {
|
|
GeyserImpl.getInstance().getLogger().error("Could not find the Java item to add custom item properties to for " + item.name());
|
|
return false;
|
|
}
|
|
if (!item.customItemOptions().hasCustomItemOptions()) {
|
|
GeyserImpl.getInstance().getLogger().error("The custom item " + item.name() + " has no registration types");
|
|
}
|
|
String name = item.name();
|
|
if (name.isEmpty()) {
|
|
GeyserImpl.getInstance().getLogger().warning("Custom item name is empty?");
|
|
} else if (Character.isDigit(name.charAt(0))) {
|
|
// As of 1.19.31
|
|
GeyserImpl.getInstance().getLogger().warning("Custom item name (" + name + ") begins with a digit. This may cause issues!");
|
|
}
|
|
return true;
|
|
}
|
|
|
|
public static NonVanillaItemRegistration registerCustomItem(NonVanillaCustomItemData customItemData, int customItemId) {
|
|
String customIdentifier = customItemData.identifier();
|
|
|
|
Set<String> repairMaterials = customItemData.repairMaterials();
|
|
|
|
Item.Builder itemBuilder = Item.builder()
|
|
.stackSize(customItemData.stackSize())
|
|
.maxDamage(customItemData.maxDamage());
|
|
Item item = new Item(customIdentifier, itemBuilder) {
|
|
@Override
|
|
public boolean isValidRepairItem(Item other) {
|
|
return repairMaterials != null && repairMaterials.contains(other.javaIdentifier());
|
|
}
|
|
};
|
|
Items.register(item, customItemData.javaId());
|
|
|
|
ItemMapping customItemMapping = ItemMapping.builder()
|
|
.bedrockDefinition(new SimpleItemDefinition(customIdentifier, customItemId, true))
|
|
.bedrockData(0)
|
|
.bedrockBlockDefinition(null)
|
|
.toolType(customItemData.toolType())
|
|
.toolTier(customItemData.toolTier())
|
|
.translationString(customItemData.translationString())
|
|
.customItemOptions(Collections.emptyList())
|
|
.javaItem(item)
|
|
.build();
|
|
|
|
NbtMapBuilder builder = createComponentNbt(customItemData, customItemData.identifier(), customItemId,
|
|
customItemData.creativeCategory(), customItemData.creativeGroup(), customItemData.isHat(), customItemData.displayHandheld());
|
|
ComponentItemData componentItemData = new ComponentItemData(customIdentifier, builder.build());
|
|
|
|
return new NonVanillaItemRegistration(componentItemData, item, customItemMapping);
|
|
}
|
|
|
|
private static NbtMapBuilder createComponentNbt(CustomItemData customItemData, Item javaItem, GeyserMappingItem mapping,
|
|
String customItemName, int customItemId) {
|
|
NbtMapBuilder builder = NbtMap.builder();
|
|
builder.putString("name", customItemName)
|
|
.putInt("id", customItemId);
|
|
|
|
NbtMapBuilder itemProperties = NbtMap.builder();
|
|
NbtMapBuilder componentBuilder = NbtMap.builder();
|
|
|
|
setupBasicItemInfo(javaItem.maxDamage(), javaItem.maxStackSize(), mapping.getToolType() != null || customItemData.displayHandheld(), customItemData, itemProperties, componentBuilder);
|
|
|
|
boolean canDestroyInCreative = true;
|
|
if (mapping.getToolType() != null) { // This is not using the isTool boolean because it is not just a render type here.
|
|
canDestroyInCreative = computeToolProperties(mapping.getToolTier(), mapping.getToolType(), itemProperties, componentBuilder);
|
|
}
|
|
itemProperties.putBoolean("can_destroy_in_creative", canDestroyInCreative);
|
|
|
|
if (mapping.getArmorType() != null) {
|
|
computeArmorProperties(mapping.getArmorType(), mapping.getProtectionValue(), componentBuilder);
|
|
}
|
|
|
|
if (mapping.getFirstBlockRuntimeId() != null) {
|
|
computeBlockItemProperties(mapping.getBedrockIdentifier(), componentBuilder);
|
|
}
|
|
|
|
if (mapping.isEdible()) {
|
|
computeConsumableProperties(itemProperties, componentBuilder, 1, false);
|
|
}
|
|
|
|
if (mapping.isEntityPlacer()) {
|
|
computeEntityPlacerProperties(componentBuilder);
|
|
}
|
|
|
|
switch (mapping.getBedrockIdentifier()) {
|
|
case "minecraft:fire_charge", "minecraft:flint_and_steel" -> {
|
|
computeBlockItemProperties("minecraft:fire", componentBuilder);
|
|
}
|
|
case "minecraft:bow", "minecraft:crossbow", "minecraft:trident" -> {
|
|
computeChargeableProperties(itemProperties, componentBuilder);
|
|
}
|
|
case "minecraft:honey_bottle", "minecraft:milk_bucket", "minecraft:potion" -> {
|
|
computeConsumableProperties(itemProperties, componentBuilder, 2, true);
|
|
}
|
|
case "minecraft:experience_bottle", "minecraft:egg", "minecraft:ender_pearl", "minecraft:ender_eye", "minecraft:lingering_potion", "minecraft:snowball", "minecraft:splash_potion" -> {
|
|
computeThrowableProperties(componentBuilder);
|
|
}
|
|
}
|
|
|
|
computeRenderOffsets(false, customItemData, componentBuilder);
|
|
|
|
componentBuilder.putCompound("item_properties", itemProperties.build());
|
|
builder.putCompound("components", componentBuilder.build());
|
|
|
|
return builder;
|
|
}
|
|
|
|
private static NbtMapBuilder createComponentNbt(NonVanillaCustomItemData customItemData, String customItemName,
|
|
int customItemId, OptionalInt creativeCategory,
|
|
String creativeGroup, boolean isHat, boolean displayHandheld) {
|
|
NbtMapBuilder builder = NbtMap.builder();
|
|
builder.putString("name", customItemName)
|
|
.putInt("id", customItemId);
|
|
|
|
NbtMapBuilder itemProperties = NbtMap.builder();
|
|
NbtMapBuilder componentBuilder = NbtMap.builder();
|
|
|
|
setupBasicItemInfo(customItemData.maxDamage(), customItemData.stackSize(), displayHandheld, customItemData, itemProperties, componentBuilder);
|
|
|
|
boolean canDestroyInCreative = true;
|
|
if (customItemData.toolType() != null) { // This is not using the isTool boolean because it is not just a render type here.
|
|
canDestroyInCreative = computeToolProperties(customItemData.toolTier(), customItemData.toolType(), itemProperties, componentBuilder);
|
|
}
|
|
itemProperties.putBoolean("can_destroy_in_creative", canDestroyInCreative);
|
|
|
|
String armorType = customItemData.armorType();
|
|
if (armorType != null) {
|
|
computeArmorProperties(armorType, customItemData.protectionValue(), componentBuilder);
|
|
}
|
|
|
|
if (customItemData.isEdible()) {
|
|
computeConsumableProperties(itemProperties, componentBuilder, 1, customItemData.canAlwaysEat());
|
|
}
|
|
|
|
if (customItemData.isChargeable()) {
|
|
computeChargeableProperties(itemProperties, componentBuilder);
|
|
}
|
|
|
|
computeRenderOffsets(isHat, customItemData, componentBuilder);
|
|
|
|
if (creativeGroup != null) {
|
|
itemProperties.putString("creative_group", creativeGroup);
|
|
}
|
|
if (creativeCategory.isPresent()) {
|
|
itemProperties.putInt("creative_category", creativeCategory.getAsInt());
|
|
}
|
|
|
|
if (customItemData.isFoil()) {
|
|
itemProperties.putBoolean("foil", true);
|
|
}
|
|
|
|
componentBuilder.putCompound("item_properties", itemProperties.build());
|
|
builder.putCompound("components", componentBuilder.build());
|
|
|
|
return builder;
|
|
}
|
|
|
|
private static void setupBasicItemInfo(int maxDamage, int stackSize, boolean displayHandheld, CustomItemData customItemData, NbtMapBuilder itemProperties, NbtMapBuilder componentBuilder) {
|
|
itemProperties.putCompound("minecraft:icon", NbtMap.builder()
|
|
.putString("texture", customItemData.icon())
|
|
.build());
|
|
componentBuilder.putCompound("minecraft:display_name", NbtMap.builder().putString("value", customItemData.displayName()).build());
|
|
|
|
itemProperties.putBoolean("allow_off_hand", customItemData.allowOffhand());
|
|
itemProperties.putBoolean("hand_equipped", displayHandheld);
|
|
itemProperties.putInt("max_stack_size", stackSize);
|
|
// Ignore durability if the item's predicate requires that it be unbreakable
|
|
if (maxDamage > 0 && customItemData.customItemOptions().unbreakable() != TriState.TRUE) {
|
|
componentBuilder.putCompound("minecraft:durability", NbtMap.builder()
|
|
.putCompound("damage_chance", NbtMap.builder()
|
|
.putInt("max", 1)
|
|
.putInt("min", 1)
|
|
.build())
|
|
.putInt("max_durability", maxDamage)
|
|
.build());
|
|
itemProperties.putBoolean("use_duration", true);
|
|
}
|
|
}
|
|
|
|
/**
|
|
* @return can destroy in creative
|
|
*/
|
|
private static boolean computeToolProperties(String toolTier, String toolType, NbtMapBuilder itemProperties, NbtMapBuilder componentBuilder) {
|
|
boolean canDestroyInCreative = true;
|
|
float miningSpeed = 1.0f;
|
|
|
|
// This means client side the tool can never destroy a block
|
|
// This works because the molang '1' for tags will be true for all blocks and the speed will be 0
|
|
// We want this since we calculate break speed server side in BedrockActionTranslator
|
|
List<NbtMap> speed = new ArrayList<>(List.of(
|
|
NbtMap.builder()
|
|
.putCompound("block", NbtMap.builder()
|
|
.putString("tags", "1")
|
|
.build())
|
|
.putCompound("on_dig", NbtMap.builder()
|
|
.putCompound("condition", NbtMap.builder()
|
|
.putString("expression", "")
|
|
.putInt("version", -1)
|
|
.build())
|
|
.putString("event", "tool_durability")
|
|
.putString("target", "self")
|
|
.build())
|
|
.putInt("speed", 0)
|
|
.build()
|
|
));
|
|
|
|
componentBuilder.putCompound("minecraft:digger",
|
|
NbtMap.builder()
|
|
.putList("destroy_speeds", NbtType.COMPOUND, speed)
|
|
.putCompound("on_dig", NbtMap.builder()
|
|
.putCompound("condition", NbtMap.builder()
|
|
.putString("expression", "")
|
|
.putInt("version", -1)
|
|
.build())
|
|
.putString("event", "tool_durability")
|
|
.putString("target", "self")
|
|
.build())
|
|
.putBoolean("use_efficiency", true)
|
|
.build()
|
|
);
|
|
|
|
if (toolType.equals("sword")) {
|
|
miningSpeed = 1.5f;
|
|
canDestroyInCreative = false;
|
|
componentBuilder.putCompound("minecraft:weapon", NbtMap.EMPTY);
|
|
}
|
|
|
|
itemProperties.putBoolean("hand_equipped", true);
|
|
itemProperties.putFloat("mining_speed", miningSpeed);
|
|
|
|
return canDestroyInCreative;
|
|
}
|
|
|
|
private static void computeArmorProperties(String armorType, int protectionValue, NbtMapBuilder componentBuilder) {
|
|
switch (armorType) {
|
|
case "boots" -> {
|
|
componentBuilder.putString("minecraft:render_offsets", "boots");
|
|
componentBuilder.putCompound("minecraft:wearable", WearableSlot.FEET.getSlotNbt());
|
|
componentBuilder.putCompound("minecraft:armor", NbtMap.builder().putInt("protection", protectionValue).build());
|
|
}
|
|
case "chestplate" -> {
|
|
componentBuilder.putString("minecraft:render_offsets", "chestplates");
|
|
componentBuilder.putCompound("minecraft:wearable", WearableSlot.CHEST.getSlotNbt());
|
|
componentBuilder.putCompound("minecraft:armor", NbtMap.builder().putInt("protection", protectionValue).build());
|
|
}
|
|
case "leggings" -> {
|
|
componentBuilder.putString("minecraft:render_offsets", "leggings");
|
|
componentBuilder.putCompound("minecraft:wearable", WearableSlot.LEGS.getSlotNbt());
|
|
componentBuilder.putCompound("minecraft:armor", NbtMap.builder().putInt("protection", protectionValue).build());
|
|
}
|
|
case "helmet" -> {
|
|
componentBuilder.putString("minecraft:render_offsets", "helmets");
|
|
componentBuilder.putCompound("minecraft:wearable", WearableSlot.HEAD.getSlotNbt());
|
|
componentBuilder.putCompound("minecraft:armor", NbtMap.builder().putInt("protection", protectionValue).build());
|
|
}
|
|
}
|
|
}
|
|
|
|
private static void computeBlockItemProperties(String blockItem, NbtMapBuilder componentBuilder) {
|
|
// carved pumpkin should be able to be worn and for that we would need to add wearable and armor with protection 0 here
|
|
// however this would have the side effect of preventing carved pumpkins from working as an attachable on the RP side outside the head slot
|
|
// it also causes the item to glitch when right clicked to "equip" so this should only be added here later if these issues can be overcome
|
|
|
|
// all block items registered should be given this component to prevent double placement
|
|
componentBuilder.putCompound("minecraft:block_placer", NbtMap.builder().putString("block", blockItem).build());
|
|
}
|
|
|
|
private static void computeChargeableProperties(NbtMapBuilder itemProperties, NbtMapBuilder componentBuilder) {
|
|
// setting high use_duration prevents the consume animation from playing
|
|
itemProperties.putInt("use_duration", Integer.MAX_VALUE);
|
|
// display item as tool (mainly for crossbow and bow)
|
|
itemProperties.putBoolean("hand_equipped", true);
|
|
// ensure client moves at slow speed while charging (note: this was calculated by hand as the movement modifer value does not seem to scale linearly)
|
|
componentBuilder.putCompound("minecraft:chargeable", NbtMap.builder().putFloat("movement_modifier", 0.35F).build());
|
|
}
|
|
|
|
private static void computeConsumableProperties(NbtMapBuilder itemProperties, NbtMapBuilder componentBuilder, int useAnimation, boolean canAlwaysEat) {
|
|
// this is the duration of the use animation in ticks; note that in behavior packs this is set as a float in seconds, but over the network it is an int in ticks
|
|
itemProperties.putInt("use_duration", 32);
|
|
// this dictates that the item will use the eat or drink animation (in the first person) and play eat or drink sounds
|
|
// note that in behavior packs this is set as the string "eat" or "drink", but over the network it as an int, with these values being 1 and 2 respectively
|
|
itemProperties.putInt("use_animation", useAnimation);
|
|
// this component is required to allow the eat animation to play
|
|
componentBuilder.putCompound("minecraft:food", NbtMap.builder().putBoolean("can_always_eat", canAlwaysEat).build());
|
|
}
|
|
|
|
private static void computeEntityPlacerProperties(NbtMapBuilder componentBuilder) {
|
|
// all items registered that place entities should be given this component to prevent double placement
|
|
// it is okay that the entity here does not match the actual one since we control what entity actually spawns
|
|
componentBuilder.putCompound("minecraft:entity_placer", NbtMap.builder().putString("entity", "minecraft:minecart").build());
|
|
}
|
|
|
|
private static void computeThrowableProperties(NbtMapBuilder componentBuilder) {
|
|
// allows item to be thrown when holding down right click (individual presses are required w/o this component)
|
|
componentBuilder.putCompound("minecraft:throwable", NbtMap.builder().putBoolean("do_swing_animation", true).build());
|
|
// this must be set to something for the swing animation to play
|
|
// it is okay that the projectile here does not match the actual one since we control what entity actually spawns
|
|
componentBuilder.putCompound("minecraft:projectile", NbtMap.builder().putString("projectile_entity", "minecraft:snowball").build());
|
|
}
|
|
|
|
private static void computeRenderOffsets(boolean isHat, CustomItemData customItemData, NbtMapBuilder componentBuilder) {
|
|
if (isHat) {
|
|
componentBuilder.remove("minecraft:render_offsets");
|
|
componentBuilder.putString("minecraft:render_offsets", "helmets");
|
|
|
|
componentBuilder.remove("minecraft:wearable");
|
|
componentBuilder.putCompound("minecraft:wearable", WearableSlot.HEAD.getSlotNbt());
|
|
}
|
|
|
|
CustomRenderOffsets renderOffsets = customItemData.renderOffsets();
|
|
if (renderOffsets != null) {
|
|
componentBuilder.remove("minecraft:render_offsets");
|
|
componentBuilder.putCompound("minecraft:render_offsets", toNbtMap(renderOffsets));
|
|
} else if (customItemData.textureSize() != 16 && !componentBuilder.containsKey("minecraft:render_offsets")) {
|
|
float scale1 = (float) (0.075 / (customItemData.textureSize() / 16f));
|
|
float scale2 = (float) (0.125 / (customItemData.textureSize() / 16f));
|
|
float scale3 = (float) (0.075 / (customItemData.textureSize() / 16f * 2.4f));
|
|
|
|
componentBuilder.putCompound("minecraft:render_offsets",
|
|
NbtMap.builder().putCompound("main_hand", NbtMap.builder()
|
|
.putCompound("first_person", xyzToScaleList(scale3, scale3, scale3))
|
|
.putCompound("third_person", xyzToScaleList(scale1, scale2, scale1)).build())
|
|
.putCompound("off_hand", NbtMap.builder()
|
|
.putCompound("first_person", xyzToScaleList(scale1, scale2, scale1))
|
|
.putCompound("third_person", xyzToScaleList(scale1, scale2, scale1)).build()).build());
|
|
}
|
|
}
|
|
|
|
private static NbtMap toNbtMap(CustomRenderOffsets renderOffsets) {
|
|
NbtMapBuilder builder = NbtMap.builder();
|
|
|
|
CustomRenderOffsets.Hand mainHand = renderOffsets.mainHand();
|
|
if (mainHand != null) {
|
|
NbtMap nbt = toNbtMap(mainHand);
|
|
if (nbt != null) {
|
|
builder.putCompound("main_hand", nbt);
|
|
}
|
|
}
|
|
CustomRenderOffsets.Hand offhand = renderOffsets.offhand();
|
|
if (offhand != null) {
|
|
NbtMap nbt = toNbtMap(offhand);
|
|
if (nbt != null) {
|
|
builder.putCompound("off_hand", nbt);
|
|
}
|
|
}
|
|
|
|
return builder.build();
|
|
}
|
|
|
|
private static NbtMap toNbtMap(CustomRenderOffsets.Hand hand) {
|
|
NbtMap firstPerson = toNbtMap(hand.firstPerson());
|
|
NbtMap thirdPerson = toNbtMap(hand.thirdPerson());
|
|
|
|
if (firstPerson == null && thirdPerson == null) {
|
|
return null;
|
|
}
|
|
|
|
NbtMapBuilder builder = NbtMap.builder();
|
|
if (firstPerson != null) {
|
|
builder.putCompound("first_person", firstPerson);
|
|
}
|
|
if (thirdPerson != null) {
|
|
builder.putCompound("third_person", thirdPerson);
|
|
}
|
|
|
|
return builder.build();
|
|
}
|
|
|
|
private static NbtMap toNbtMap(@Nullable CustomRenderOffsets.Offset offset) {
|
|
if (offset == null) {
|
|
return null;
|
|
}
|
|
|
|
CustomRenderOffsets.OffsetXYZ position = offset.position();
|
|
CustomRenderOffsets.OffsetXYZ rotation = offset.rotation();
|
|
CustomRenderOffsets.OffsetXYZ scale = offset.scale();
|
|
|
|
if (position == null && rotation == null && scale == null) {
|
|
return null;
|
|
}
|
|
|
|
NbtMapBuilder builder = NbtMap.builder();
|
|
if (position != null) {
|
|
builder.putList("position", NbtType.FLOAT, toList(position));
|
|
}
|
|
if (rotation != null) {
|
|
builder.putList("rotation", NbtType.FLOAT, toList(rotation));
|
|
}
|
|
if (scale != null) {
|
|
builder.putList("scale", NbtType.FLOAT, toList(scale));
|
|
}
|
|
|
|
return builder.build();
|
|
}
|
|
|
|
private static List<Float> toList(CustomRenderOffsets.OffsetXYZ xyz) {
|
|
return List.of(xyz.x(), xyz.y(), xyz.z());
|
|
}
|
|
|
|
private static void setItemTag(NbtMapBuilder builder, String tag) {
|
|
builder.putList("item_tags", NbtType.STRING, List.of("minecraft:is_" + tag));
|
|
}
|
|
|
|
private static NbtMap xyzToScaleList(float x, float y, float z) {
|
|
return NbtMap.builder().putList("scale", NbtType.FLOAT, List.of(x, y, z)).build();
|
|
}
|
|
}
|