mirror of
https://github.com/GeyserMC/Geyser.git
synced 2025-03-13 11:18:19 +01:00
More stuff in the custom item registry populator
This commit is contained in:
parent
7fabf0c28d
commit
04d7f48645
5 changed files with 877 additions and 791 deletions
|
@ -148,10 +148,6 @@ public class Item {
|
|||
.definition(mapping.getBedrockDefinition())
|
||||
.damage(mapping.getBedrockData())
|
||||
.count(count);
|
||||
|
||||
ItemTranslator.translateCustomItem(session, count, components, builder, mapping);
|
||||
|
||||
return builder;
|
||||
}
|
||||
|
||||
public @NonNull GeyserItemStack translateToJava(GeyserSession session, @NonNull ItemData itemData, @NonNull ItemMapping mapping, @NonNull ItemMappings mappings) {
|
||||
|
|
|
@ -1,5 +1,5 @@
|
|||
/*
|
||||
* Copyright (c) 2019-2024 GeyserMC. http://geysermc.org
|
||||
* Copyright (c) 2024 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
|
||||
|
@ -26,6 +26,8 @@
|
|||
package org.geysermc.geyser.registry.populator;
|
||||
|
||||
import com.google.common.collect.Multimap;
|
||||
import net.kyori.adventure.key.Key;
|
||||
import org.checkerframework.checker.nullness.qual.NonNull;
|
||||
import org.checkerframework.checker.nullness.qual.Nullable;
|
||||
import org.cloudburstmc.nbt.NbtMap;
|
||||
import org.cloudburstmc.nbt.NbtMapBuilder;
|
||||
|
@ -37,35 +39,80 @@ 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.api.item.custom.v2.BedrockCreativeTab;
|
||||
import org.geysermc.geyser.api.item.custom.v2.CustomItemBedrockOptions;
|
||||
import org.geysermc.geyser.api.item.custom.v2.CustomItemDefinition;
|
||||
import org.geysermc.geyser.api.item.custom.v2.predicate.CustomItemPredicate;
|
||||
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 org.geysermc.mcprotocollib.protocol.data.game.item.component.Consumable;
|
||||
import org.geysermc.mcprotocollib.protocol.data.game.item.component.DataComponentType;
|
||||
import org.geysermc.mcprotocollib.protocol.data.game.item.component.DataComponents;
|
||||
import org.geysermc.mcprotocollib.protocol.data.game.item.component.Equippable;
|
||||
import org.geysermc.mcprotocollib.protocol.data.game.item.component.FoodProperties;
|
||||
import org.geysermc.mcprotocollib.protocol.data.game.item.component.UseCooldown;
|
||||
|
||||
import java.util.ArrayList;
|
||||
import java.util.HashMap;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
import java.util.Objects;
|
||||
import java.util.Optional;
|
||||
import java.util.Set;
|
||||
|
||||
public class CustomItemRegistryPopulator {
|
||||
public static void populate(Map<String, GeyserMappingItem> items, Multimap<String, CustomItemData> customItems, List<NonVanillaCustomItemData> nonVanillaCustomItems) {
|
||||
// In behaviour packs and Java components this is set to a text value, such as "eat" or "drink"; over Bedrock network it's sent as an int.
|
||||
// TODO these don't seem to be applying correctly
|
||||
private static final Map<Consumable.ItemUseAnimation, Integer> BEDROCK_ANIMATIONS = Map.of(
|
||||
Consumable.ItemUseAnimation.NONE, 0,
|
||||
Consumable.ItemUseAnimation.EAT, 1,
|
||||
Consumable.ItemUseAnimation.DRINK, 2,
|
||||
Consumable.ItemUseAnimation.BLOCK, 3,
|
||||
Consumable.ItemUseAnimation.BOW, 4,
|
||||
Consumable.ItemUseAnimation.SPEAR, 6,
|
||||
Consumable.ItemUseAnimation.CROSSBOW, 9,
|
||||
Consumable.ItemUseAnimation.SPYGLASS, 10,
|
||||
Consumable.ItemUseAnimation.BRUSH, 12
|
||||
);
|
||||
|
||||
public static void populate(Map<String, GeyserMappingItem> items, Multimap<String, CustomItemDefinition> customItems, List<NonVanillaCustomItemData> nonVanillaCustomItems /* TODO */) {
|
||||
// TODO
|
||||
// TODO better error handling?
|
||||
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);
|
||||
//} // TODO
|
||||
mappingsConfigReader.loadItemMappingsFromJson((id, item) -> {
|
||||
if (initialCheck(id, item, customItems, items)) {
|
||||
customItems.get(id).add(item);
|
||||
}
|
||||
});
|
||||
|
||||
GeyserImpl.getInstance().eventBus().fire(new GeyserDefineCustomItemsEventImpl(customItems, nonVanillaCustomItems) {
|
||||
|
||||
@Override
|
||||
@Deprecated
|
||||
public boolean register(@NonNull String identifier, @NonNull CustomItemData customItemData) {
|
||||
return register(identifier, customItemData.toDefinition(identifier).build());
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean register(@NonNull String identifier, @NonNull CustomItemDefinition definition) {
|
||||
if (initialCheck(identifier, definition, customItems, items)) {
|
||||
customItems.get(identifier).add(definition);
|
||||
return true;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean register(@NonNull NonVanillaCustomItemData customItemData) {
|
||||
// TODO
|
||||
return false;
|
||||
}
|
||||
});
|
||||
|
||||
int customItemCount = customItems.size() + nonVanillaCustomItems.size();
|
||||
if (customItemCount > 0) {
|
||||
|
@ -73,108 +120,133 @@ public class CustomItemRegistryPopulator {
|
|||
}
|
||||
}
|
||||
|
||||
public static GeyserCustomMappingData registerCustomItem(String customItemName, Item javaItem, GeyserMappingItem mapping, CustomItemData customItemData, int bedrockId, int protocolVersion) {
|
||||
public static GeyserCustomMappingData registerCustomItem(String customItemName, Item javaItem, GeyserMappingItem mapping,
|
||||
CustomItemDefinition customItemDefinition, int bedrockId) {
|
||||
ItemDefinition itemDefinition = new SimpleItemDefinition(customItemName, bedrockId, true);
|
||||
|
||||
NbtMapBuilder builder = createComponentNbt(customItemData, javaItem, mapping, customItemName, bedrockId, protocolVersion);
|
||||
NbtMapBuilder builder = createComponentNbt(customItemDefinition, 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) {
|
||||
private static boolean initialCheck(String identifier, CustomItemDefinition item, Multimap<String, CustomItemDefinition> registered, Map<String, GeyserMappingItem> mappings) {
|
||||
// TODO check if there's already a same model without predicate and this hasn't a predicate either
|
||||
if (!mappings.containsKey(identifier)) {
|
||||
GeyserImpl.getInstance().getLogger().error("Could not find the Java item to add custom item properties to for " + item.name());
|
||||
GeyserImpl.getInstance().getLogger().error("Could not find the Java item to add custom item properties to for " + item.bedrockIdentifier());
|
||||
return false;
|
||||
}
|
||||
if (!item.customItemOptions().hasCustomItemOptions()) {
|
||||
GeyserImpl.getInstance().getLogger().error("The custom item " + item.name() + " has no registration types");
|
||||
Key bedrockIdentifier = item.bedrockIdentifier();
|
||||
if (bedrockIdentifier.namespace().equals(Key.MINECRAFT_NAMESPACE)) {
|
||||
GeyserImpl.getInstance().getLogger().error("Custom item bedrock identifier namespace can't be minecraft");
|
||||
return false;
|
||||
} else if (item.model().namespace().equals(Key.MINECRAFT_NAMESPACE) && item.predicates().isEmpty()) {
|
||||
GeyserImpl.getInstance().getLogger().error("Custom item definition model can't be in the minecraft namespace without a predicate");
|
||||
return false;
|
||||
}
|
||||
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!");
|
||||
|
||||
for (Map.Entry<String, CustomItemDefinition> entry : registered.entries()) {
|
||||
if (entry.getValue().bedrockIdentifier().equals(item.bedrockIdentifier())) {
|
||||
GeyserImpl.getInstance().getLogger().error("Duplicate custom item definition for Bedrock ID " + item.bedrockIdentifier());
|
||||
return false;
|
||||
}
|
||||
Optional<String> error = checkPredicate(entry, identifier, item);
|
||||
if (error.isPresent()) {
|
||||
GeyserImpl.getInstance().getLogger().error("An existing item definition for the Java item " + identifier + " was already registered that conflicts with this one!");
|
||||
GeyserImpl.getInstance().getLogger().error("First entry: " + entry.getValue().bedrockIdentifier());
|
||||
GeyserImpl.getInstance().getLogger().error("Second entry: " + item.bedrockIdentifier());
|
||||
GeyserImpl.getInstance().getLogger().error(error.orElseThrow());
|
||||
}
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
public static NonVanillaItemRegistration registerCustomItem(NonVanillaCustomItemData customItemData, int customItemId, int protocolVersion) {
|
||||
String customIdentifier = customItemData.identifier();
|
||||
/**
|
||||
* Returns an error message if there was a conflict, or an empty optional otherwise
|
||||
*/
|
||||
// TODO maybe simplify this
|
||||
private static Optional<String> checkPredicate(Map.Entry<String, CustomItemDefinition> existing, String identifier, CustomItemDefinition item) {
|
||||
// If the definitions are for different Java items or models then it doesn't matter
|
||||
if (!identifier.equals(existing.getKey()) || !item.model().equals(existing.getValue().model())) {
|
||||
return Optional.empty();
|
||||
}
|
||||
// If they both don't have predicates they conflict
|
||||
if (existing.getValue().predicates().isEmpty() && item.predicates().isEmpty()) {
|
||||
return Optional.of("Both entries don't have predicates, one must have a predicate");
|
||||
}
|
||||
// If their predicates are equal then they also conflict
|
||||
if (existing.getValue().predicates().size() == item.predicates().size()) {
|
||||
boolean equal = true;
|
||||
for (CustomItemPredicate predicate : existing.getValue().predicates()) {
|
||||
if (!item.predicates().contains(predicate)) {
|
||||
equal = false;
|
||||
}
|
||||
}
|
||||
if (equal) {
|
||||
return Optional.of("Both entries have the same predicates");
|
||||
}
|
||||
}
|
||||
|
||||
DataComponents components = new DataComponents(new HashMap<>());
|
||||
components.put(DataComponentType.MAX_STACK_SIZE, customItemData.stackSize());
|
||||
components.put(DataComponentType.MAX_DAMAGE, customItemData.maxDamage());
|
||||
|
||||
Item item = new Item(customIdentifier, Item.builder().components(components));
|
||||
Items.register(item, customItemData.javaId());
|
||||
|
||||
ItemMapping customItemMapping = ItemMapping.builder()
|
||||
.bedrockDefinition(new SimpleItemDefinition(customIdentifier, customItemId, true))
|
||||
.bedrockData(0)
|
||||
.bedrockBlockDefinition(null)
|
||||
.toolType(customItemData.toolType())
|
||||
.translationString(customItemData.translationString())
|
||||
.customItemDefinitions(null)
|
||||
.javaItem(item)
|
||||
.build();
|
||||
|
||||
NbtMapBuilder builder = createComponentNbt(customItemData, customItemData.identifier(), customItemId,
|
||||
customItemData.isHat(), customItemData.displayHandheld(), protocolVersion);
|
||||
ComponentItemData componentItemData = new ComponentItemData(customIdentifier, builder.build());
|
||||
|
||||
return new NonVanillaItemRegistration(componentItemData, item, customItemMapping);
|
||||
return Optional.empty();
|
||||
}
|
||||
|
||||
private static NbtMapBuilder createComponentNbt(CustomItemData customItemData, Item javaItem, GeyserMappingItem mapping,
|
||||
String customItemName, int customItemId, int protocolVersion) {
|
||||
NbtMapBuilder builder = NbtMap.builder();
|
||||
builder.putString("name", customItemName)
|
||||
.putInt("id", customItemId);
|
||||
public static NonVanillaItemRegistration registerCustomItem(NonVanillaCustomItemData customItemData, int customItemId, int protocolVersion) {
|
||||
// TODO
|
||||
return null;
|
||||
}
|
||||
|
||||
private static NbtMapBuilder createComponentNbt(CustomItemDefinition customItemDefinition, Item vanillaJavaItem, GeyserMappingItem vanillaMapping,
|
||||
String customItemName, int customItemId) {
|
||||
NbtMapBuilder builder = NbtMap.builder()
|
||||
.putString("name", customItemName)
|
||||
.putInt("id", customItemId);
|
||||
|
||||
NbtMapBuilder itemProperties = NbtMap.builder();
|
||||
NbtMapBuilder componentBuilder = NbtMap.builder();
|
||||
|
||||
setupBasicItemInfo(javaItem.defaultMaxDamage(), javaItem.defaultMaxStackSize(), mapping.getToolType() != null || customItemData.displayHandheld(), customItemData, itemProperties, componentBuilder, protocolVersion);
|
||||
DataComponents components = patchDataComponents(vanillaJavaItem, customItemDefinition);
|
||||
setupBasicItemInfo(customItemDefinition, components, 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.getToolType(), itemProperties, componentBuilder, javaItem.defaultAttackDamage());
|
||||
if (vanillaMapping.getToolType() != null) {
|
||||
canDestroyInCreative = computeToolProperties(vanillaMapping.getToolType(), itemProperties, componentBuilder, vanillaJavaItem.defaultAttackDamage());
|
||||
}
|
||||
itemProperties.putBoolean("can_destroy_in_creative", canDestroyInCreative);
|
||||
|
||||
if (mapping.getArmorType() != null) {
|
||||
computeArmorProperties(mapping.getArmorType(), mapping.getProtectionValue(), itemProperties, componentBuilder);
|
||||
Equippable equippable = components.get(DataComponentType.EQUIPPABLE);
|
||||
if (equippable != null) {
|
||||
computeArmorProperties(equippable, itemProperties, componentBuilder);
|
||||
}
|
||||
|
||||
if (mapping.getFirstBlockRuntimeId() != null) {
|
||||
computeBlockItemProperties(mapping.getBedrockIdentifier(), componentBuilder);
|
||||
if (vanillaMapping.getFirstBlockRuntimeId() != null) {
|
||||
computeBlockItemProperties(vanillaMapping.getBedrockIdentifier(), componentBuilder);
|
||||
}
|
||||
|
||||
if (mapping.isEdible()) {
|
||||
computeConsumableProperties(itemProperties, componentBuilder, 1, false);
|
||||
Consumable consumable = components.get(DataComponentType.CONSUMABLE);
|
||||
if (consumable != null) {
|
||||
FoodProperties foodProperties = components.get(DataComponentType.FOOD);
|
||||
computeConsumableProperties(consumable, foodProperties == null || foodProperties.isCanAlwaysEat(), itemProperties, componentBuilder);
|
||||
}
|
||||
|
||||
if (mapping.isEntityPlacer()) {
|
||||
if (vanillaMapping.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, mapping.getBedrockIdentifier(), protocolVersion);
|
||||
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);
|
||||
UseCooldown useCooldown = components.get(DataComponentType.USE_COOLDOWN);
|
||||
if (useCooldown != null) {
|
||||
computeUseCooldownProperties(useCooldown, componentBuilder);
|
||||
}
|
||||
|
||||
// Hardcoded on Java, and should extend to the custom item
|
||||
boolean isHat = (javaItem.equals(Items.SKELETON_SKULL) || javaItem.equals(Items.WITHER_SKELETON_SKULL)
|
||||
|| javaItem.equals(Items.CARVED_PUMPKIN) || javaItem.equals(Items.ZOMBIE_HEAD)
|
||||
|| javaItem.equals(Items.PIGLIN_HEAD) || javaItem.equals(Items.DRAGON_HEAD)
|
||||
|| javaItem.equals(Items.CREEPER_HEAD) || javaItem.equals(Items.PLAYER_HEAD)
|
||||
);
|
||||
computeRenderOffsets(isHat, customItemData, componentBuilder);
|
||||
// TODO not really a fan of this switch statement
|
||||
switch (vanillaMapping.getBedrockIdentifier()) {
|
||||
case "minecraft:fire_charge", "minecraft:flint_and_steel" -> computeBlockItemProperties("minecraft:fire", componentBuilder);
|
||||
case "minecraft:bow", "minecraft:crossbow", "minecraft:trident" -> computeChargeableProperties(itemProperties, componentBuilder, vanillaMapping.getBedrockIdentifier());
|
||||
case "minecraft:experience_bottle", "minecraft:egg", "minecraft:ender_pearl", "minecraft:ender_eye", "minecraft:lingering_potion", "minecraft:snowball", "minecraft:splash_potion" ->
|
||||
computeThrowableProperties(componentBuilder);
|
||||
}
|
||||
|
||||
computeRenderOffsets(customItemDefinition.bedrockOptions(), componentBuilder); // TODO check "hats" the hardcoded ones, once default components are here, check stack size
|
||||
|
||||
componentBuilder.putCompound("item_properties", itemProperties.build());
|
||||
builder.putCompound("components", componentBuilder.build());
|
||||
|
@ -184,100 +256,62 @@ public class CustomItemRegistryPopulator {
|
|||
|
||||
private static NbtMapBuilder createComponentNbt(NonVanillaCustomItemData customItemData, String customItemName,
|
||||
int customItemId, boolean isHat, boolean displayHandheld, int protocolVersion) {
|
||||
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, protocolVersion);
|
||||
|
||||
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(Objects.requireNonNull(customItemData.toolType()), itemProperties, componentBuilder, customItemData.attackDamage());
|
||||
}
|
||||
itemProperties.putBoolean("can_destroy_in_creative", canDestroyInCreative);
|
||||
|
||||
String armorType = customItemData.armorType();
|
||||
if (armorType != null) {
|
||||
computeArmorProperties(armorType, customItemData.protectionValue(), itemProperties, componentBuilder);
|
||||
}
|
||||
|
||||
if (customItemData.isEdible()) {
|
||||
computeConsumableProperties(itemProperties, componentBuilder, 1, customItemData.canAlwaysEat());
|
||||
}
|
||||
|
||||
if (customItemData.isChargeable()) {
|
||||
String tooltype = customItemData.toolType();
|
||||
if (tooltype == null) {
|
||||
throw new IllegalArgumentException("tool type must be set if the custom item is chargeable!");
|
||||
}
|
||||
computeChargeableProperties(itemProperties, componentBuilder, "minecraft:" + tooltype, protocolVersion);
|
||||
}
|
||||
|
||||
computeRenderOffsets(isHat, customItemData, componentBuilder);
|
||||
|
||||
if (customItemData.isFoil()) {
|
||||
itemProperties.putBoolean("foil", true);
|
||||
}
|
||||
|
||||
String block = customItemData.block();
|
||||
if (block != null) {
|
||||
computeBlockItemProperties(block, componentBuilder);
|
||||
}
|
||||
|
||||
componentBuilder.putCompound("item_properties", itemProperties.build());
|
||||
builder.putCompound("components", componentBuilder.build());
|
||||
|
||||
return builder;
|
||||
// TODO;
|
||||
return null;
|
||||
}
|
||||
|
||||
private static void setupBasicItemInfo(int maxDamage, int stackSize, boolean displayHandheld, CustomItemData customItemData, NbtMapBuilder itemProperties, NbtMapBuilder componentBuilder, int protocolVersion) {
|
||||
private static void setupBasicItemInfo(CustomItemDefinition definition, DataComponents components, NbtMapBuilder itemProperties, NbtMapBuilder componentBuilder) {
|
||||
CustomItemBedrockOptions options = definition.bedrockOptions();
|
||||
NbtMap iconMap = NbtMap.builder()
|
||||
.putCompound("textures", NbtMap.builder()
|
||||
.putString("default", customItemData.icon())
|
||||
.build())
|
||||
.putString("default", definition.icon())
|
||||
.build())
|
||||
.build();
|
||||
itemProperties.putCompound("minecraft:icon", iconMap);
|
||||
|
||||
if (customItemData.creativeCategory().isPresent()) {
|
||||
itemProperties.putInt("creative_category", customItemData.creativeCategory().getAsInt());
|
||||
if (options.creativeCategory() != BedrockCreativeTab.NONE) {
|
||||
itemProperties.putInt("creative_category", options.creativeCategory().ordinal());
|
||||
|
||||
if (customItemData.creativeGroup() != null) {
|
||||
itemProperties.putString("creative_group", customItemData.creativeGroup());
|
||||
if (options.creativeGroup() != null) {
|
||||
itemProperties.putString("creative_group", options.creativeGroup());
|
||||
}
|
||||
}
|
||||
|
||||
componentBuilder.putCompound("minecraft:display_name", NbtMap.builder().putString("value", customItemData.displayName()).build());
|
||||
componentBuilder.putCompound("minecraft:display_name", NbtMap.builder().putString("value", definition.displayName()).build());
|
||||
|
||||
// Add a Geyser tag to the item, allowing Molang queries
|
||||
addItemTag(componentBuilder, "geyser:is_custom");
|
||||
|
||||
// Add other defined tags to the item
|
||||
Set<String> tags = customItemData.tags();
|
||||
Set<String> tags = options.tags();
|
||||
for (String tag : tags) {
|
||||
if (tag != null && !tag.isBlank()) {
|
||||
addItemTag(componentBuilder, tag);
|
||||
}
|
||||
}
|
||||
|
||||
itemProperties.putBoolean("allow_off_hand", customItemData.allowOffhand());
|
||||
itemProperties.putBoolean("hand_equipped", displayHandheld);
|
||||
itemProperties.putBoolean("allow_off_hand", options.allowOffhand());
|
||||
itemProperties.putBoolean("hand_equipped", options.displayHandheld());
|
||||
|
||||
int maxDamage = components.getOrDefault(DataComponentType.MAX_DAMAGE, 0);
|
||||
Equippable equippable = components.get(DataComponentType.EQUIPPABLE);
|
||||
// Java requires stack size to be 1 when max damage is above 0, and bedrock requires stack size to be 1 when the item can be equipped
|
||||
int stackSize = maxDamage > 0 || equippable != null ? 1 : components.getOrDefault(DataComponentType.MAX_STACK_SIZE, 0); // This should never be 0 since we're patching components on top of the vanilla one's
|
||||
|
||||
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) {
|
||||
if (maxDamage > 0/* && customItemData.customItemOptions().unbreakable() != TriState.TRUE*/) { // TODO Insert check back in once predicates are here?
|
||||
componentBuilder.putCompound("minecraft:durability", NbtMap.builder()
|
||||
.putCompound("damage_chance", NbtMap.builder()
|
||||
.putInt("max", 1)
|
||||
.putInt("min", 1)
|
||||
.build())
|
||||
.putInt("max_durability", maxDamage)
|
||||
.build());
|
||||
.putCompound("damage_chance", NbtMap.builder()
|
||||
.putInt("max", 1)
|
||||
.putInt("min", 1)
|
||||
.build())
|
||||
.putInt("max_durability", maxDamage)
|
||||
.build());
|
||||
itemProperties.putBoolean("use_duration", true);
|
||||
}
|
||||
}
|
||||
|
||||
// TODO minecraft java tool component - also needs work elsewhere to calculate correct break speed (server authorised block breaking)
|
||||
/**
|
||||
* @return can destroy in creative
|
||||
*/
|
||||
|
@ -291,33 +325,33 @@ public class CustomItemRegistryPopulator {
|
|||
List<NbtMap> speed = new ArrayList<>(List.of(
|
||||
NbtMap.builder()
|
||||
.putCompound("block", NbtMap.builder()
|
||||
.putString("tags", "1")
|
||||
.build())
|
||||
.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("expression", "")
|
||||
.putInt("version", -1)
|
||||
.build())
|
||||
.putString("event", "tool_durability")
|
||||
.putString("target", "self")
|
||||
.build())
|
||||
.putBoolean("use_efficiency", true)
|
||||
.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")) {
|
||||
|
@ -340,39 +374,38 @@ public class CustomItemRegistryPopulator {
|
|||
return canDestroyInCreative;
|
||||
}
|
||||
|
||||
private static void computeArmorProperties(String armorType, int protectionValue, NbtMapBuilder itemProperties, NbtMapBuilder componentBuilder) {
|
||||
switch (armorType) {
|
||||
case "boots" -> {
|
||||
private static void computeArmorProperties(Equippable equippable, /*String armorType, int protectionValue,*/ NbtMapBuilder itemProperties, NbtMapBuilder componentBuilder) {
|
||||
int protectionValue = 0;
|
||||
// TODO protection value, check if it's just visual or not, also enchantable stuff
|
||||
switch (equippable.slot()) {
|
||||
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());
|
||||
|
||||
itemProperties.putString("enchantable_slot", "armor_feet");
|
||||
itemProperties.putInt("enchantable_value", 15);
|
||||
//itemProperties.putString("enchantable_slot", "armor_feet");
|
||||
//itemProperties.putInt("enchantable_value", 15); TODO
|
||||
}
|
||||
case "chestplate" -> {
|
||||
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());
|
||||
|
||||
itemProperties.putString("enchantable_slot", "armor_torso");
|
||||
itemProperties.putInt("enchantable_value", 15);
|
||||
//itemProperties.putString("enchantable_slot", "armor_torso");
|
||||
//itemProperties.putInt("enchantable_value", 15); TODO
|
||||
}
|
||||
case "leggings" -> {
|
||||
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());
|
||||
|
||||
itemProperties.putString("enchantable_slot", "armor_legs");
|
||||
itemProperties.putInt("enchantable_value", 15);
|
||||
//itemProperties.putString("enchantable_slot", "armor_legs");
|
||||
//itemProperties.putInt("enchantable_value", 15); TODO
|
||||
}
|
||||
case "helmet" -> {
|
||||
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());
|
||||
//componentBuilder.putCompound("minecraft:armor", NbtMap.builder().putInt("protection", protectionValue).build());
|
||||
|
||||
itemProperties.putString("enchantable_slot", "armor_head");
|
||||
itemProperties.putInt("enchantable_value", 15);
|
||||
//itemProperties.putString("enchantable_slot", "armor_head");
|
||||
//itemProperties.putInt("enchantable_value", 15);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -386,7 +419,8 @@ public class CustomItemRegistryPopulator {
|
|||
componentBuilder.putCompound("minecraft:block_placer", NbtMap.builder().putString("block", blockItem).build());
|
||||
}
|
||||
|
||||
private static void computeChargeableProperties(NbtMapBuilder itemProperties, NbtMapBuilder componentBuilder, String mapping, int protocolVersion) {
|
||||
// TODO this isn't right
|
||||
private static void computeChargeableProperties(NbtMapBuilder itemProperties, NbtMapBuilder componentBuilder, String mapping) {
|
||||
// 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)
|
||||
|
@ -395,9 +429,9 @@ public class CustomItemRegistryPopulator {
|
|||
itemProperties.putInt("enchantable_value", 1);
|
||||
|
||||
componentBuilder.putCompound("minecraft:use_modifiers", NbtMap.builder()
|
||||
.putFloat("use_duration", 100F)
|
||||
.putFloat("movement_modifier", 0.35F)
|
||||
.build());
|
||||
.putFloat("use_duration", 100F)
|
||||
.putFloat("movement_modifier", 0.35F)
|
||||
.build());
|
||||
|
||||
switch (mapping) {
|
||||
case "minecraft:bow" -> {
|
||||
|
@ -405,19 +439,19 @@ public class CustomItemRegistryPopulator {
|
|||
itemProperties.putInt("frame_count", 3);
|
||||
|
||||
componentBuilder.putCompound("minecraft:shooter", NbtMap.builder()
|
||||
.putList("ammunition", NbtType.COMPOUND, List.of(
|
||||
NbtMap.builder()
|
||||
.putCompound("item", NbtMap.builder()
|
||||
.putString("name", "minecraft:arrow")
|
||||
.build())
|
||||
.putBoolean("use_offhand", true)
|
||||
.putBoolean("search_inventory", true)
|
||||
.build()
|
||||
))
|
||||
.putFloat("max_draw_duration", 0f)
|
||||
.putBoolean("charge_on_draw", true)
|
||||
.putBoolean("scale_power_by_draw_duration", true)
|
||||
.build());
|
||||
.putList("ammunition", NbtType.COMPOUND, List.of(
|
||||
NbtMap.builder()
|
||||
.putCompound("item", NbtMap.builder()
|
||||
.putString("name", "minecraft:arrow")
|
||||
.build())
|
||||
.putBoolean("use_offhand", true)
|
||||
.putBoolean("search_inventory", true)
|
||||
.build()
|
||||
))
|
||||
.putFloat("max_draw_duration", 0f)
|
||||
.putBoolean("charge_on_draw", true)
|
||||
.putBoolean("scale_power_by_draw_duration", true)
|
||||
.build());
|
||||
componentBuilder.putInt("minecraft:use_duration", 999);
|
||||
}
|
||||
case "minecraft:trident" -> {
|
||||
|
@ -429,40 +463,44 @@ public class CustomItemRegistryPopulator {
|
|||
itemProperties.putInt("frame_count", 10);
|
||||
|
||||
componentBuilder.putCompound("minecraft:shooter", NbtMap.builder()
|
||||
.putList("ammunition", NbtType.COMPOUND, List.of(
|
||||
NbtMap.builder()
|
||||
.putCompound("item", NbtMap.builder()
|
||||
.putString("name", "minecraft:arrow")
|
||||
.build())
|
||||
.putBoolean("use_offhand", true)
|
||||
.putBoolean("search_inventory", true)
|
||||
.build()
|
||||
))
|
||||
.putFloat("max_draw_duration", 1f)
|
||||
.putBoolean("charge_on_draw", true)
|
||||
.putBoolean("scale_power_by_draw_duration", true)
|
||||
.build());
|
||||
.putList("ammunition", NbtType.COMPOUND, List.of(
|
||||
NbtMap.builder()
|
||||
.putCompound("item", NbtMap.builder()
|
||||
.putString("name", "minecraft:arrow")
|
||||
.build())
|
||||
.putBoolean("use_offhand", true)
|
||||
.putBoolean("search_inventory", true)
|
||||
.build()
|
||||
))
|
||||
.putFloat("max_draw_duration", 1f)
|
||||
.putBoolean("charge_on_draw", true)
|
||||
.putBoolean("scale_power_by_draw_duration", true)
|
||||
.build());
|
||||
componentBuilder.putInt("minecraft:use_duration", 999);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private static void computeConsumableProperties(NbtMapBuilder itemProperties, NbtMapBuilder componentBuilder, int useAnimation, boolean canAlwaysEat) {
|
||||
private static void computeConsumableProperties(Consumable consumable, boolean canAlwaysEat, NbtMapBuilder itemProperties, NbtMapBuilder componentBuilder) {
|
||||
// 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);
|
||||
itemProperties.putInt("use_duration", (int) (consumable.consumeSeconds() * 20));
|
||||
|
||||
itemProperties.putInt("use_animation", BEDROCK_ANIMATIONS.get(consumable.animation()));
|
||||
componentBuilder.putCompound("minecraft:use_animation", NbtMap.builder()
|
||||
.putString("value", consumable.animation().toString().toLowerCase())
|
||||
.build()); // TODO check
|
||||
|
||||
// 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
|
||||
// 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());
|
||||
}
|
||||
|
||||
// TODO this also probably isn't right
|
||||
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());
|
||||
|
@ -471,31 +509,32 @@ public class CustomItemRegistryPopulator {
|
|||
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");
|
||||
private static void computeUseCooldownProperties(UseCooldown cooldown, NbtMapBuilder componentBuilder) {
|
||||
Objects.requireNonNull(cooldown.cooldownGroup(), "Cooldown group can't be null");
|
||||
componentBuilder.putCompound("minecraft:cooldown", NbtMap.builder()
|
||||
.putString("category", cooldown.cooldownGroup().asString())
|
||||
.putFloat("duration", cooldown.seconds())
|
||||
.build()
|
||||
);
|
||||
}
|
||||
|
||||
componentBuilder.remove("minecraft:wearable");
|
||||
componentBuilder.putCompound("minecraft:wearable", WearableSlot.HEAD.getSlotNbt());
|
||||
}
|
||||
|
||||
CustomRenderOffsets renderOffsets = customItemData.renderOffsets();
|
||||
private static void computeRenderOffsets(CustomItemBedrockOptions bedrockOptions, NbtMapBuilder componentBuilder) {
|
||||
CustomRenderOffsets renderOffsets = bedrockOptions.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));
|
||||
} else if (bedrockOptions.textureSize() != 16 && !componentBuilder.containsKey("minecraft:render_offsets")) {
|
||||
float scale1 = (float) (0.075 / (bedrockOptions.textureSize() / 16f));
|
||||
float scale2 = (float) (0.125 / (bedrockOptions.textureSize() / 16f));
|
||||
float scale3 = (float) (0.075 / (bedrockOptions.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());
|
||||
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());
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -570,6 +609,15 @@ public class CustomItemRegistryPopulator {
|
|||
return List.of(xyz.x(), xyz.y(), xyz.z());
|
||||
}
|
||||
|
||||
private static NbtMap xyzToScaleList(float x, float y, float z) {
|
||||
return NbtMap.builder().putList("scale", NbtType.FLOAT, List.of(x, y, z)).build();
|
||||
}
|
||||
|
||||
// TODO is this right?
|
||||
private static DataComponents patchDataComponents(Item javaItem, CustomItemDefinition definition) {
|
||||
return javaItem.gatherComponents(definition.components());
|
||||
}
|
||||
|
||||
@SuppressWarnings("unchecked")
|
||||
private static void addItemTag(NbtMapBuilder builder, String tag) {
|
||||
List<String> tagList = (List<String>) builder.get("item_tags");
|
||||
|
@ -584,8 +632,4 @@ public class CustomItemRegistryPopulator {
|
|||
}
|
||||
}
|
||||
}
|
||||
|
||||
private static NbtMap xyzToScaleList(float x, float y, float z) {
|
||||
return NbtMap.builder().putList("scale", NbtType.FLOAT, List.of(x, y, z)).build();
|
||||
}
|
||||
}
|
||||
|
|
|
@ -0,0 +1,591 @@
|
|||
/*
|
||||
* Copyright (c) 2019-2024 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.Nullable;
|
||||
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.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 org.geysermc.mcprotocollib.protocol.data.game.item.component.DataComponentType;
|
||||
import org.geysermc.mcprotocollib.protocol.data.game.item.component.DataComponents;
|
||||
|
||||
import java.util.ArrayList;
|
||||
import java.util.HashMap;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
import java.util.Objects;
|
||||
import java.util.Set;
|
||||
|
||||
public class CustomItemRegistryPopulator_v1 {
|
||||
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);
|
||||
//} // TODO
|
||||
});
|
||||
|
||||
|
||||
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, int protocolVersion) {
|
||||
ItemDefinition itemDefinition = new SimpleItemDefinition(customItemName, bedrockId, true);
|
||||
|
||||
NbtMapBuilder builder = createComponentNbt(customItemData, javaItem, mapping, customItemName, bedrockId, protocolVersion);
|
||||
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, int protocolVersion) {
|
||||
String customIdentifier = customItemData.identifier();
|
||||
|
||||
DataComponents components = new DataComponents(new HashMap<>());
|
||||
components.put(DataComponentType.MAX_STACK_SIZE, customItemData.stackSize());
|
||||
components.put(DataComponentType.MAX_DAMAGE, customItemData.maxDamage());
|
||||
|
||||
Item item = new Item(customIdentifier, Item.builder().components(components));
|
||||
Items.register(item, customItemData.javaId());
|
||||
|
||||
ItemMapping customItemMapping = ItemMapping.builder()
|
||||
.bedrockDefinition(new SimpleItemDefinition(customIdentifier, customItemId, true))
|
||||
.bedrockData(0)
|
||||
.bedrockBlockDefinition(null)
|
||||
.toolType(customItemData.toolType())
|
||||
.translationString(customItemData.translationString())
|
||||
.customItemDefinitions(null)
|
||||
.javaItem(item)
|
||||
.build();
|
||||
|
||||
NbtMapBuilder builder = createComponentNbt(customItemData, customItemData.identifier(), customItemId,
|
||||
customItemData.isHat(), customItemData.displayHandheld(), protocolVersion);
|
||||
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, int protocolVersion) {
|
||||
NbtMapBuilder builder = NbtMap.builder();
|
||||
builder.putString("name", customItemName)
|
||||
.putInt("id", customItemId);
|
||||
|
||||
NbtMapBuilder itemProperties = NbtMap.builder();
|
||||
NbtMapBuilder componentBuilder = NbtMap.builder();
|
||||
|
||||
setupBasicItemInfo(javaItem.defaultMaxDamage(), javaItem.defaultMaxStackSize(), mapping.getToolType() != null || customItemData.displayHandheld(), customItemData, itemProperties, componentBuilder, protocolVersion);
|
||||
|
||||
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.getToolType(), itemProperties, componentBuilder, javaItem.defaultAttackDamage());
|
||||
}
|
||||
itemProperties.putBoolean("can_destroy_in_creative", canDestroyInCreative);
|
||||
|
||||
if (mapping.getArmorType() != null) {
|
||||
computeArmorProperties(mapping.getArmorType(), mapping.getProtectionValue(), itemProperties, 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, mapping.getBedrockIdentifier(), protocolVersion);
|
||||
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);
|
||||
}
|
||||
|
||||
// Hardcoded on Java, and should extend to the custom item
|
||||
boolean isHat = (javaItem.equals(Items.SKELETON_SKULL) || javaItem.equals(Items.WITHER_SKELETON_SKULL)
|
||||
|| javaItem.equals(Items.CARVED_PUMPKIN) || javaItem.equals(Items.ZOMBIE_HEAD)
|
||||
|| javaItem.equals(Items.PIGLIN_HEAD) || javaItem.equals(Items.DRAGON_HEAD)
|
||||
|| javaItem.equals(Items.CREEPER_HEAD) || javaItem.equals(Items.PLAYER_HEAD)
|
||||
);
|
||||
computeRenderOffsets(isHat, 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, boolean isHat, boolean displayHandheld, int protocolVersion) {
|
||||
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, protocolVersion);
|
||||
|
||||
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(Objects.requireNonNull(customItemData.toolType()), itemProperties, componentBuilder, customItemData.attackDamage());
|
||||
}
|
||||
itemProperties.putBoolean("can_destroy_in_creative", canDestroyInCreative);
|
||||
|
||||
String armorType = customItemData.armorType();
|
||||
if (armorType != null) {
|
||||
computeArmorProperties(armorType, customItemData.protectionValue(), itemProperties, componentBuilder);
|
||||
}
|
||||
|
||||
if (customItemData.isEdible()) {
|
||||
computeConsumableProperties(itemProperties, componentBuilder, 1, customItemData.canAlwaysEat());
|
||||
}
|
||||
|
||||
if (customItemData.isChargeable()) {
|
||||
String tooltype = customItemData.toolType();
|
||||
if (tooltype == null) {
|
||||
throw new IllegalArgumentException("tool type must be set if the custom item is chargeable!");
|
||||
}
|
||||
computeChargeableProperties(itemProperties, componentBuilder, "minecraft:" + tooltype, protocolVersion);
|
||||
}
|
||||
|
||||
computeRenderOffsets(isHat, customItemData, componentBuilder);
|
||||
|
||||
if (customItemData.isFoil()) {
|
||||
itemProperties.putBoolean("foil", true);
|
||||
}
|
||||
|
||||
String block = customItemData.block();
|
||||
if (block != null) {
|
||||
computeBlockItemProperties(block, componentBuilder);
|
||||
}
|
||||
|
||||
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, int protocolVersion) {
|
||||
NbtMap iconMap = NbtMap.builder()
|
||||
.putCompound("textures", NbtMap.builder()
|
||||
.putString("default", customItemData.icon())
|
||||
.build())
|
||||
.build();
|
||||
itemProperties.putCompound("minecraft:icon", iconMap);
|
||||
|
||||
if (customItemData.creativeCategory().isPresent()) {
|
||||
itemProperties.putInt("creative_category", customItemData.creativeCategory().getAsInt());
|
||||
|
||||
if (customItemData.creativeGroup() != null) {
|
||||
itemProperties.putString("creative_group", customItemData.creativeGroup());
|
||||
}
|
||||
}
|
||||
|
||||
componentBuilder.putCompound("minecraft:display_name", NbtMap.builder().putString("value", customItemData.displayName()).build());
|
||||
|
||||
// Add a Geyser tag to the item, allowing Molang queries
|
||||
addItemTag(componentBuilder, "geyser:is_custom");
|
||||
|
||||
// Add other defined tags to the item
|
||||
Set<String> tags = customItemData.tags();
|
||||
for (String tag : tags) {
|
||||
if (tag != null && !tag.isBlank()) {
|
||||
addItemTag(componentBuilder, tag);
|
||||
}
|
||||
}
|
||||
|
||||
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 toolType, NbtMapBuilder itemProperties, NbtMapBuilder componentBuilder, int attackDamage) {
|
||||
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;
|
||||
}
|
||||
|
||||
itemProperties.putBoolean("hand_equipped", true);
|
||||
itemProperties.putFloat("mining_speed", miningSpeed);
|
||||
|
||||
// This allows custom tools - shears, swords, shovels, axes etc to be enchanted or combined in the anvil
|
||||
itemProperties.putInt("enchantable_value", 1);
|
||||
itemProperties.putString("enchantable_slot", toolType);
|
||||
|
||||
// Adds a "attack damage" indicator. Purely visual!
|
||||
if (attackDamage > 0) {
|
||||
itemProperties.putInt("damage", attackDamage);
|
||||
}
|
||||
|
||||
return canDestroyInCreative;
|
||||
}
|
||||
|
||||
private static void computeArmorProperties(String armorType, int protectionValue, NbtMapBuilder itemProperties, 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());
|
||||
|
||||
itemProperties.putString("enchantable_slot", "armor_feet");
|
||||
itemProperties.putInt("enchantable_value", 15);
|
||||
}
|
||||
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());
|
||||
|
||||
itemProperties.putString("enchantable_slot", "armor_torso");
|
||||
itemProperties.putInt("enchantable_value", 15);
|
||||
}
|
||||
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());
|
||||
|
||||
itemProperties.putString("enchantable_slot", "armor_legs");
|
||||
itemProperties.putInt("enchantable_value", 15);
|
||||
}
|
||||
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());
|
||||
|
||||
itemProperties.putString("enchantable_slot", "armor_head");
|
||||
itemProperties.putInt("enchantable_value", 15);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
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, String mapping, int protocolVersion) {
|
||||
// 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);
|
||||
// Make bows, tridents, and crossbows enchantable
|
||||
itemProperties.putInt("enchantable_value", 1);
|
||||
|
||||
componentBuilder.putCompound("minecraft:use_modifiers", NbtMap.builder()
|
||||
.putFloat("use_duration", 100F)
|
||||
.putFloat("movement_modifier", 0.35F)
|
||||
.build());
|
||||
|
||||
switch (mapping) {
|
||||
case "minecraft:bow" -> {
|
||||
itemProperties.putString("enchantable_slot", "bow");
|
||||
itemProperties.putInt("frame_count", 3);
|
||||
|
||||
componentBuilder.putCompound("minecraft:shooter", NbtMap.builder()
|
||||
.putList("ammunition", NbtType.COMPOUND, List.of(
|
||||
NbtMap.builder()
|
||||
.putCompound("item", NbtMap.builder()
|
||||
.putString("name", "minecraft:arrow")
|
||||
.build())
|
||||
.putBoolean("use_offhand", true)
|
||||
.putBoolean("search_inventory", true)
|
||||
.build()
|
||||
))
|
||||
.putFloat("max_draw_duration", 0f)
|
||||
.putBoolean("charge_on_draw", true)
|
||||
.putBoolean("scale_power_by_draw_duration", true)
|
||||
.build());
|
||||
componentBuilder.putInt("minecraft:use_duration", 999);
|
||||
}
|
||||
case "minecraft:trident" -> {
|
||||
itemProperties.putString("enchantable_slot", "trident");
|
||||
componentBuilder.putInt("minecraft:use_duration", 999);
|
||||
}
|
||||
case "minecraft:crossbow" -> {
|
||||
itemProperties.putString("enchantable_slot", "crossbow");
|
||||
itemProperties.putInt("frame_count", 10);
|
||||
|
||||
componentBuilder.putCompound("minecraft:shooter", NbtMap.builder()
|
||||
.putList("ammunition", NbtType.COMPOUND, List.of(
|
||||
NbtMap.builder()
|
||||
.putCompound("item", NbtMap.builder()
|
||||
.putString("name", "minecraft:arrow")
|
||||
.build())
|
||||
.putBoolean("use_offhand", true)
|
||||
.putBoolean("search_inventory", true)
|
||||
.build()
|
||||
))
|
||||
.putFloat("max_draw_duration", 1f)
|
||||
.putBoolean("charge_on_draw", true)
|
||||
.putBoolean("scale_power_by_draw_duration", true)
|
||||
.build());
|
||||
componentBuilder.putInt("minecraft:use_duration", 999);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
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 @Nullable 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 @Nullable NbtMap toNbtMap(CustomRenderOffsets.@Nullable 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());
|
||||
}
|
||||
|
||||
@SuppressWarnings("unchecked")
|
||||
private static void addItemTag(NbtMapBuilder builder, String tag) {
|
||||
List<String> tagList = (List<String>) builder.get("item_tags");
|
||||
if (tagList == null) {
|
||||
builder.putList("item_tags", NbtType.STRING, tag);
|
||||
} else {
|
||||
// NbtList is immutable
|
||||
if (!tagList.contains(tag)) {
|
||||
tagList = new ArrayList<>(tagList);
|
||||
tagList.add(tag);
|
||||
builder.putList("item_tags", NbtType.STRING, tagList);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private static NbtMap xyzToScaleList(float x, float y, float z) {
|
||||
return NbtMap.builder().putList("scale", NbtType.FLOAT, List.of(x, y, z)).build();
|
||||
}
|
||||
}
|
|
@ -1,542 +0,0 @@
|
|||
/*
|
||||
* Copyright (c) 2024 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 net.kyori.adventure.key.Key;
|
||||
import org.checkerframework.checker.nullness.qual.NonNull;
|
||||
import org.checkerframework.checker.nullness.qual.Nullable;
|
||||
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.item.custom.v2.BedrockCreativeTab;
|
||||
import org.geysermc.geyser.api.item.custom.v2.CustomItemBedrockOptions;
|
||||
import org.geysermc.geyser.api.item.custom.v2.CustomItemDefinition;
|
||||
import org.geysermc.geyser.api.item.custom.v2.predicate.CustomItemPredicate;
|
||||
import org.geysermc.geyser.event.type.GeyserDefineCustomItemsEventImpl;
|
||||
import org.geysermc.geyser.item.GeyserCustomMappingData;
|
||||
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.mcprotocollib.protocol.data.game.item.component.Consumable;
|
||||
import org.geysermc.mcprotocollib.protocol.data.game.item.component.DataComponentType;
|
||||
import org.geysermc.mcprotocollib.protocol.data.game.item.component.DataComponents;
|
||||
import org.geysermc.mcprotocollib.protocol.data.game.item.component.Equippable;
|
||||
import org.geysermc.mcprotocollib.protocol.data.game.item.component.FoodProperties;
|
||||
import org.geysermc.mcprotocollib.protocol.data.game.item.component.UseCooldown;
|
||||
|
||||
import java.util.ArrayList;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
import java.util.Objects;
|
||||
import java.util.Optional;
|
||||
import java.util.Set;
|
||||
|
||||
public class CustomItemRegistryPopulator_v2 {
|
||||
// In behaviour packs and Java components this is set to a text value, such as "eat" or "drink"; over Bedrock network it's sent as an int.
|
||||
private static final Map<Consumable.ItemUseAnimation, Integer> BEDROCK_ANIMATIONS = Map.of(
|
||||
Consumable.ItemUseAnimation.NONE, 0,
|
||||
Consumable.ItemUseAnimation.EAT, 1,
|
||||
Consumable.ItemUseAnimation.DRINK, 2,
|
||||
Consumable.ItemUseAnimation.BLOCK, 3,
|
||||
Consumable.ItemUseAnimation.BOW, 4,
|
||||
Consumable.ItemUseAnimation.SPEAR, 6,
|
||||
Consumable.ItemUseAnimation.CROSSBOW, 9,
|
||||
Consumable.ItemUseAnimation.SPYGLASS, 10,
|
||||
Consumable.ItemUseAnimation.BRUSH, 12
|
||||
);
|
||||
|
||||
public static void populate(Map<String, GeyserMappingItem> items, Multimap<String, CustomItemDefinition> customItems, List<NonVanillaCustomItemData> nonVanillaCustomItems /* TODO */) {
|
||||
// TODO
|
||||
// TODO better error handling?
|
||||
MappingsConfigReader mappingsConfigReader = new MappingsConfigReader();
|
||||
// Load custom items from mappings files
|
||||
mappingsConfigReader.loadItemMappingsFromJson((id, item) -> {
|
||||
if (initialCheck(id, item, customItems, items)) {
|
||||
customItems.get(id).add(item);
|
||||
}
|
||||
});
|
||||
|
||||
GeyserImpl.getInstance().eventBus().fire(new GeyserDefineCustomItemsEventImpl(customItems, nonVanillaCustomItems) {
|
||||
|
||||
@Override
|
||||
@Deprecated
|
||||
public boolean register(@NonNull String identifier, @NonNull CustomItemData customItemData) {
|
||||
return register(identifier, customItemData.toDefinition(identifier).build());
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean register(@NonNull String identifier, @NonNull CustomItemDefinition definition) {
|
||||
if (initialCheck(identifier, definition, customItems, items)) {
|
||||
customItems.get(identifier).add(definition);
|
||||
return true;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean register(@NonNull NonVanillaCustomItemData customItemData) {
|
||||
// TODO
|
||||
return false;
|
||||
}
|
||||
});
|
||||
|
||||
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,
|
||||
CustomItemDefinition customItemDefinition, int bedrockId) {
|
||||
ItemDefinition itemDefinition = new SimpleItemDefinition(customItemName, bedrockId, true);
|
||||
|
||||
NbtMapBuilder builder = createComponentNbt(customItemDefinition, javaItem, mapping, customItemName, bedrockId);
|
||||
ComponentItemData componentItemData = new ComponentItemData(customItemName, builder.build());
|
||||
|
||||
return new GeyserCustomMappingData(componentItemData, itemDefinition, customItemName, bedrockId);
|
||||
}
|
||||
|
||||
private static boolean initialCheck(String identifier, CustomItemDefinition item, Multimap<String, CustomItemDefinition> registered, Map<String, GeyserMappingItem> mappings) {
|
||||
// TODO check if there's already a same model without predicate and this hasn't a predicate either
|
||||
if (!mappings.containsKey(identifier)) {
|
||||
GeyserImpl.getInstance().getLogger().error("Could not find the Java item to add custom item properties to for " + item.bedrockIdentifier());
|
||||
return false;
|
||||
}
|
||||
Key bedrockIdentifier = item.bedrockIdentifier();
|
||||
if (bedrockIdentifier.namespace().equals(Key.MINECRAFT_NAMESPACE)) {
|
||||
GeyserImpl.getInstance().getLogger().error("Custom item bedrock identifier namespace can't be minecraft");
|
||||
return false;
|
||||
} else if (item.model().namespace().equals(Key.MINECRAFT_NAMESPACE) && item.predicates().isEmpty()) {
|
||||
GeyserImpl.getInstance().getLogger().error("Custom item definition model can't be in the minecraft namespace without a predicate");
|
||||
return false;
|
||||
}
|
||||
|
||||
for (Map.Entry<String, CustomItemDefinition> entry : registered.entries()) {
|
||||
if (entry.getValue().bedrockIdentifier().equals(item.bedrockIdentifier())) {
|
||||
GeyserImpl.getInstance().getLogger().error("Duplicate custom item definition for Bedrock ID " + item.bedrockIdentifier());
|
||||
return false;
|
||||
}
|
||||
Optional<String> error = checkPredicate(entry, identifier, item);
|
||||
if (error.isPresent()) {
|
||||
GeyserImpl.getInstance().getLogger().error("An existing item definition for the Java item " + identifier + " was already registered that conflicts with this one!");
|
||||
GeyserImpl.getInstance().getLogger().error("First entry: " + entry.getValue().bedrockIdentifier());
|
||||
GeyserImpl.getInstance().getLogger().error("Second entry: " + item.bedrockIdentifier());
|
||||
GeyserImpl.getInstance().getLogger().error(error.orElseThrow());
|
||||
}
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns an error message if there was a conflict, or an empty optional otherwise
|
||||
*/
|
||||
// TODO maybe simplify this
|
||||
private static Optional<String> checkPredicate(Map.Entry<String, CustomItemDefinition> existing, String identifier, CustomItemDefinition item) {
|
||||
// If the definitions are for different Java items or models then it doesn't matter
|
||||
if (!identifier.equals(existing.getKey()) || !item.model().equals(existing.getValue().model())) {
|
||||
return Optional.empty();
|
||||
}
|
||||
// If they both don't have predicates they conflict
|
||||
if (existing.getValue().predicates().isEmpty() && item.predicates().isEmpty()) {
|
||||
return Optional.of("Both entries don't have predicates, one must have a predicate");
|
||||
}
|
||||
// If their predicates are equal then they also conflict
|
||||
if (existing.getValue().predicates().size() == item.predicates().size()) {
|
||||
boolean equal = true;
|
||||
for (CustomItemPredicate predicate : existing.getValue().predicates()) {
|
||||
if (!item.predicates().contains(predicate)) {
|
||||
equal = false;
|
||||
}
|
||||
}
|
||||
if (equal) {
|
||||
return Optional.of("Both entries have the same predicates");
|
||||
}
|
||||
}
|
||||
|
||||
return Optional.empty();
|
||||
}
|
||||
|
||||
private static NbtMapBuilder createComponentNbt(CustomItemDefinition customItemDefinition, Item vanillaJavaItem, GeyserMappingItem vanillaMapping,
|
||||
String customItemName, int customItemId) {
|
||||
NbtMapBuilder builder = NbtMap.builder()
|
||||
.putString("name", customItemName)
|
||||
.putInt("id", customItemId);
|
||||
|
||||
NbtMapBuilder itemProperties = NbtMap.builder();
|
||||
NbtMapBuilder componentBuilder = NbtMap.builder();
|
||||
|
||||
DataComponents components = patchDataComponents(vanillaJavaItem, customItemDefinition);
|
||||
setupBasicItemInfo(customItemDefinition, components, itemProperties, componentBuilder);
|
||||
|
||||
boolean canDestroyInCreative = true;
|
||||
if (vanillaMapping.getToolType() != null) { // This is not using the isTool boolean because it is not just a render type here.
|
||||
canDestroyInCreative = computeToolProperties(vanillaMapping.getToolType(), itemProperties, componentBuilder, vanillaJavaItem.defaultAttackDamage());
|
||||
}
|
||||
itemProperties.putBoolean("can_destroy_in_creative", canDestroyInCreative);
|
||||
|
||||
Equippable equippable = components.get(DataComponentType.EQUIPPABLE);
|
||||
if (equippable != null) {
|
||||
computeArmorProperties(equippable, itemProperties, componentBuilder);
|
||||
}
|
||||
|
||||
if (vanillaMapping.getFirstBlockRuntimeId() != null) {
|
||||
computeBlockItemProperties(vanillaMapping.getBedrockIdentifier(), componentBuilder);
|
||||
}
|
||||
|
||||
Consumable consumable = components.get(DataComponentType.CONSUMABLE);
|
||||
if (consumable != null) {
|
||||
FoodProperties foodProperties = components.get(DataComponentType.FOOD);
|
||||
computeConsumableProperties(consumable, foodProperties == null || foodProperties.isCanAlwaysEat(), itemProperties, componentBuilder);
|
||||
}
|
||||
|
||||
if (vanillaMapping.isEntityPlacer()) {
|
||||
computeEntityPlacerProperties(componentBuilder);
|
||||
}
|
||||
|
||||
UseCooldown useCooldown = components.get(DataComponentType.USE_COOLDOWN);
|
||||
if (useCooldown != null) {
|
||||
computeUseCooldownProperties(useCooldown, componentBuilder);
|
||||
}
|
||||
|
||||
// TODO that switch statement
|
||||
|
||||
computeRenderOffsets(customItemDefinition.bedrockOptions(), componentBuilder); // TODO check "hats" the hardcoded ones, once default components are here, check stack size
|
||||
|
||||
componentBuilder.putCompound("item_properties", itemProperties.build());
|
||||
builder.putCompound("components", componentBuilder.build());
|
||||
|
||||
return builder;
|
||||
}
|
||||
|
||||
private static void setupBasicItemInfo(CustomItemDefinition definition, DataComponents components, NbtMapBuilder itemProperties, NbtMapBuilder componentBuilder) {
|
||||
CustomItemBedrockOptions options = definition.bedrockOptions();
|
||||
NbtMap iconMap = NbtMap.builder()
|
||||
.putCompound("textures", NbtMap.builder()
|
||||
.putString("default", definition.icon())
|
||||
.build())
|
||||
.build();
|
||||
itemProperties.putCompound("minecraft:icon", iconMap);
|
||||
|
||||
if (options.creativeCategory() != BedrockCreativeTab.NONE) {
|
||||
itemProperties.putInt("creative_category", options.creativeCategory().ordinal());
|
||||
|
||||
if (options.creativeGroup() != null) {
|
||||
itemProperties.putString("creative_group", options.creativeGroup());
|
||||
}
|
||||
}
|
||||
|
||||
componentBuilder.putCompound("minecraft:display_name", NbtMap.builder().putString("value", definition.displayName()).build());
|
||||
|
||||
// Add a Geyser tag to the item, allowing Molang queries
|
||||
addItemTag(componentBuilder, "geyser:is_custom");
|
||||
|
||||
// Add other defined tags to the item
|
||||
Set<String> tags = options.tags();
|
||||
for (String tag : tags) {
|
||||
if (tag != null && !tag.isBlank()) {
|
||||
addItemTag(componentBuilder, tag);
|
||||
}
|
||||
}
|
||||
|
||||
itemProperties.putBoolean("allow_off_hand", options.allowOffhand());
|
||||
itemProperties.putBoolean("hand_equipped", options.displayHandheld());
|
||||
|
||||
int maxDamage = components.getOrDefault(DataComponentType.MAX_DAMAGE, 0);
|
||||
Equippable equippable = components.get(DataComponentType.EQUIPPABLE);
|
||||
// Java requires stack size to be 1 when max damage is above 0, and bedrock requires stack size to be 1 when the item can be equipped
|
||||
int stackSize = maxDamage > 0 || equippable != null ? 1 : components.getOrDefault(DataComponentType.MAX_STACK_SIZE, 0); // This should never be 0 since we're patching components on top of the vanilla one's
|
||||
|
||||
itemProperties.putInt("max_stack_size", stackSize);
|
||||
if (maxDamage > 0/* && customItemData.customItemOptions().unbreakable() != TriState.TRUE*/) { // TODO Insert check back in once predicates are here?
|
||||
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);
|
||||
}
|
||||
}
|
||||
|
||||
// TODO minecraft java tool component - also needs work elsewhere to calculate correct break speed (server authorised block breaking)
|
||||
private static boolean computeToolProperties(String toolType, NbtMapBuilder itemProperties, NbtMapBuilder componentBuilder, int attackDamage) {
|
||||
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;
|
||||
}
|
||||
|
||||
itemProperties.putBoolean("hand_equipped", true);
|
||||
itemProperties.putFloat("mining_speed", miningSpeed);
|
||||
|
||||
// This allows custom tools - shears, swords, shovels, axes etc to be enchanted or combined in the anvil
|
||||
itemProperties.putInt("enchantable_value", 1);
|
||||
itemProperties.putString("enchantable_slot", toolType);
|
||||
|
||||
// Adds a "attack damage" indicator. Purely visual!
|
||||
if (attackDamage > 0) {
|
||||
itemProperties.putInt("damage", attackDamage);
|
||||
}
|
||||
|
||||
return canDestroyInCreative;
|
||||
}
|
||||
|
||||
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 computeArmorProperties(Equippable equippable, /*String armorType, int protectionValue,*/ NbtMapBuilder itemProperties, NbtMapBuilder componentBuilder) {
|
||||
int protectionValue = 0;
|
||||
// TODO protection value
|
||||
switch (equippable.slot()) {
|
||||
case BOOTS -> {
|
||||
componentBuilder.putString("minecraft:render_offsets", "boots");
|
||||
componentBuilder.putCompound("minecraft:wearable", WearableSlot.FEET.getSlotNbt());
|
||||
|
||||
//itemProperties.putString("enchantable_slot", "armor_feet");
|
||||
//itemProperties.putInt("enchantable_value", 15); TODO
|
||||
}
|
||||
case CHESTPLATE -> {
|
||||
componentBuilder.putString("minecraft:render_offsets", "chestplates");
|
||||
componentBuilder.putCompound("minecraft:wearable", WearableSlot.CHEST.getSlotNbt());
|
||||
|
||||
//itemProperties.putString("enchantable_slot", "armor_torso");
|
||||
//itemProperties.putInt("enchantable_value", 15); TODO
|
||||
}
|
||||
case LEGGINGS -> {
|
||||
componentBuilder.putString("minecraft:render_offsets", "leggings");
|
||||
componentBuilder.putCompound("minecraft:wearable", WearableSlot.LEGS.getSlotNbt());
|
||||
|
||||
//itemProperties.putString("enchantable_slot", "armor_legs");
|
||||
//itemProperties.putInt("enchantable_value", 15); TODO
|
||||
}
|
||||
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());
|
||||
|
||||
//itemProperties.putString("enchantable_slot", "armor_head");
|
||||
//itemProperties.putInt("enchantable_value", 15);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private static void computeConsumableProperties(Consumable consumable, boolean canAlwaysEat, NbtMapBuilder itemProperties, NbtMapBuilder componentBuilder) {
|
||||
// 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", (int) (consumable.consumeSeconds() * 20));
|
||||
|
||||
itemProperties.putInt("use_animation", BEDROCK_ANIMATIONS.get(consumable.animation()));
|
||||
componentBuilder.putCompound("minecraft:use_animation", NbtMap.builder()
|
||||
.putString("value", consumable.animation().toString().toLowerCase())
|
||||
.build()); // TODO check
|
||||
|
||||
// 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 computeUseCooldownProperties(UseCooldown cooldown, NbtMapBuilder componentBuilder) {
|
||||
Objects.requireNonNull(cooldown.cooldownGroup(), "Cooldown group can't be null");
|
||||
componentBuilder.putCompound("minecraft:cooldown", NbtMap.builder()
|
||||
.putString("category", cooldown.cooldownGroup().asString())
|
||||
.putFloat("duration", cooldown.seconds())
|
||||
.build()
|
||||
);
|
||||
}
|
||||
|
||||
private static void computeRenderOffsets(CustomItemBedrockOptions bedrockOptions, NbtMapBuilder componentBuilder) {
|
||||
CustomRenderOffsets renderOffsets = bedrockOptions.renderOffsets();
|
||||
if (renderOffsets != null) {
|
||||
componentBuilder.remove("minecraft:render_offsets");
|
||||
componentBuilder.putCompound("minecraft:render_offsets", toNbtMap(renderOffsets));
|
||||
} else if (bedrockOptions.textureSize() != 16 && !componentBuilder.containsKey("minecraft:render_offsets")) {
|
||||
float scale1 = (float) (0.075 / (bedrockOptions.textureSize() / 16f));
|
||||
float scale2 = (float) (0.125 / (bedrockOptions.textureSize() / 16f));
|
||||
float scale3 = (float) (0.075 / (bedrockOptions.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 @Nullable 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 @Nullable NbtMap toNbtMap(CustomRenderOffsets.@Nullable 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 NbtMap xyzToScaleList(float x, float y, float z) {
|
||||
return NbtMap.builder().putList("scale", NbtType.FLOAT, List.of(x, y, z)).build();
|
||||
}
|
||||
|
||||
// TODO is this right?
|
||||
private static DataComponents patchDataComponents(Item javaItem, CustomItemDefinition definition) {
|
||||
return javaItem.gatherComponents(definition.components());
|
||||
}
|
||||
|
||||
@SuppressWarnings("unchecked")
|
||||
private static void addItemTag(NbtMapBuilder builder, String tag) {
|
||||
List<String> tagList = (List<String>) builder.get("item_tags");
|
||||
if (tagList == null) {
|
||||
builder.putList("item_tags", NbtType.STRING, tag);
|
||||
} else {
|
||||
// NbtList is immutable
|
||||
if (!tagList.contains(tag)) {
|
||||
tagList = new ArrayList<>(tagList);
|
||||
tagList.add(tag);
|
||||
builder.putList("item_tags", NbtType.STRING, tagList);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
|
@ -174,14 +174,11 @@ public class ItemRegistryPopulator {
|
|||
|
||||
boolean customItemsAllowed = GeyserImpl.getInstance().getConfig().isAddNonBedrockItems();
|
||||
|
||||
// List values here is important compared to HashSet - we need to preserve the order of what's given to us
|
||||
// (as of 1.19.2 Java) to replicate some edge cases in Java predicate behavior where it checks from the bottom
|
||||
// of the list first, then ascends.
|
||||
Multimap<String, CustomItemDefinition> customItems = MultimapBuilder.hashKeys().arrayListValues().build();
|
||||
List<NonVanillaCustomItemData> nonVanillaCustomItems = customItemsAllowed ? new ObjectArrayList<>() : Collections.emptyList();
|
||||
|
||||
if (customItemsAllowed) {
|
||||
CustomItemRegistryPopulator_v2.populate(items, customItems, nonVanillaCustomItems);
|
||||
CustomItemRegistryPopulator.populate(items, customItems, nonVanillaCustomItems);
|
||||
}
|
||||
|
||||
// We can reduce some operations as Java information is the same across all palette versions
|
||||
|
@ -494,7 +491,7 @@ public class ItemRegistryPopulator {
|
|||
continue;
|
||||
}
|
||||
|
||||
GeyserCustomMappingData customMapping = CustomItemRegistryPopulator_v2.registerCustomItem(
|
||||
GeyserCustomMappingData customMapping = CustomItemRegistryPopulator.registerCustomItem(
|
||||
customItemName, javaItem, mappingItem, customItem, customProtocolId);
|
||||
|
||||
if (customItem.bedrockOptions().creativeCategory() != BedrockCreativeTab.NONE) {
|
||||
|
@ -597,7 +594,7 @@ public class ItemRegistryPopulator {
|
|||
}
|
||||
|
||||
int customItemId = nextFreeBedrockId++;
|
||||
NonVanillaItemRegistration registration = CustomItemRegistryPopulator.registerCustomItem(customItem, customItemId, palette.protocolVersion);
|
||||
NonVanillaItemRegistration registration = CustomItemRegistryPopulator_v1.registerCustomItem(customItem, customItemId, palette.protocolVersion);
|
||||
|
||||
componentItemData.add(registration.componentItemData());
|
||||
ItemMapping mapping = registration.mapping();
|
||||
|
|
Loading…
Add table
Reference in a new issue