2022-07-02 16:50:16 +00:00
/ *
2024-03-31 11:01:59 +00:00
* Copyright ( c ) 2019 - 2024 GeyserMC . http : //geysermc.org
2022-07-02 16:50:16 +00:00
*
* 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 ;
2022-10-30 16:34:08 +00:00
import com.google.common.collect.Multimap ;
2022-12-29 20:10:40 +00:00
import org.checkerframework.checker.nullness.qual.NonNull ;
2023-12-05 23:54:42 +00:00
import org.checkerframework.checker.nullness.qual.Nullable ;
2022-12-21 00:47:45 +00:00
import org.cloudburstmc.nbt.NbtMap ;
import org.cloudburstmc.nbt.NbtMapBuilder ;
import org.cloudburstmc.nbt.NbtType ;
2023-06-03 09:47:50 +00:00
import org.cloudburstmc.protocol.bedrock.data.definitions.ItemDefinition ;
import org.cloudburstmc.protocol.bedrock.data.definitions.SimpleItemDefinition ;
2022-10-30 00:23:21 +00:00
import org.cloudburstmc.protocol.bedrock.data.inventory.ComponentItemData ;
2022-07-02 16:50:16 +00:00
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 ;
2022-10-18 03:36:46 +00:00
import org.geysermc.geyser.api.util.TriState ;
2022-10-30 16:34:08 +00:00
import org.geysermc.geyser.event.type.GeyserDefineCustomItemsEventImpl ;
2022-07-02 16:50:16 +00:00
import org.geysermc.geyser.item.GeyserCustomMappingData ;
2022-12-29 20:10:40 +00:00
import org.geysermc.geyser.item.Items ;
2022-07-02 16:50:16 +00:00
import org.geysermc.geyser.item.components.WearableSlot ;
2022-12-29 20:10:40 +00:00
import org.geysermc.geyser.item.type.Item ;
2024-02-06 19:11:17 +00:00
import org.geysermc.geyser.network.GameProtocol ;
2023-08-21 23:04:08 +00:00
import org.geysermc.geyser.registry.mappings.MappingsConfigReader ;
2022-07-02 16:50:16 +00:00
import org.geysermc.geyser.registry.type.GeyserMappingItem ;
import org.geysermc.geyser.registry.type.ItemMapping ;
import org.geysermc.geyser.registry.type.NonVanillaItemRegistration ;
2022-12-31 01:57:38 +00:00
import java.util.* ;
2022-07-02 16:50:16 +00:00
public class CustomItemRegistryPopulator {
2022-10-30 16:34:08 +00:00
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
2023-08-21 23:04:08 +00:00
mappingsConfigReader . loadItemMappingsFromJson ( ( key , item ) - > {
2022-10-30 16:34:08 +00:00
if ( CustomItemRegistryPopulator . initialCheck ( key , item , items ) ) {
customItems . get ( key ) . add ( item ) ;
}
} ) ;
GeyserImpl . getInstance ( ) . eventBus ( ) . fire ( new GeyserDefineCustomItemsEventImpl ( customItems , nonVanillaCustomItems ) {
@Override
public boolean register ( @NonNull String identifier , @NonNull CustomItemData customItemData ) {
if ( CustomItemRegistryPopulator . initialCheck ( identifier , customItemData , items ) ) {
customItems . get ( identifier ) . add ( customItemData ) ;
return true ;
}
return false ;
}
@Override
public boolean register ( @NonNull NonVanillaCustomItemData customItemData ) {
if ( customItemData . identifier ( ) . startsWith ( " minecraft: " ) ) {
GeyserImpl . getInstance ( ) . getLogger ( ) . error ( " The custom item " + customItemData . identifier ( ) +
" is attempting to masquerade as a vanilla Minecraft item! " ) ;
return false ;
}
if ( customItemData . javaId ( ) < items . size ( ) ) {
// Attempting to overwrite an item that already exists in the protocol
GeyserImpl . getInstance ( ) . getLogger ( ) . error ( " The custom item " + customItemData . identifier ( ) +
" is attempting to overwrite a vanilla Minecraft item! " ) ;
return false ;
}
nonVanillaCustomItems . add ( customItemData ) ;
return true ;
}
} ) ;
int customItemCount = customItems . size ( ) + nonVanillaCustomItems . size ( ) ;
if ( customItemCount > 0 ) {
GeyserImpl . getInstance ( ) . getLogger ( ) . info ( " Registered " + customItemCount + " custom items " ) ;
}
}
2024-02-06 19:11:17 +00:00
public static GeyserCustomMappingData registerCustomItem ( String customItemName , Item javaItem , GeyserMappingItem mapping , CustomItemData customItemData , int bedrockId , int protocolVersion ) {
2022-12-29 20:10:40 +00:00
ItemDefinition itemDefinition = new SimpleItemDefinition ( customItemName , bedrockId , true ) ;
2022-07-02 16:50:16 +00:00
2024-02-06 19:11:17 +00:00
NbtMapBuilder builder = createComponentNbt ( customItemData , javaItem , mapping , customItemName , bedrockId , protocolVersion ) ;
2022-07-02 16:50:16 +00:00
ComponentItemData componentItemData = new ComponentItemData ( customItemName , builder . build ( ) ) ;
2022-10-30 00:23:21 +00:00
return new GeyserCustomMappingData ( componentItemData , itemDefinition , customItemName , bedrockId ) ;
2022-07-02 16:50:16 +00:00
}
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 " ) ;
}
2022-10-21 18:09:17 +00:00
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! " ) ;
}
2022-07-02 16:50:16 +00:00
return true ;
}
2024-02-06 19:11:17 +00:00
public static NonVanillaItemRegistration registerCustomItem ( NonVanillaCustomItemData customItemData , int customItemId , int protocolVersion ) {
2022-07-02 16:50:16 +00:00
String customIdentifier = customItemData . identifier ( ) ;
2022-12-31 01:57:38 +00:00
Set < String > repairMaterials = customItemData . repairMaterials ( ) ;
2022-12-29 20:10:40 +00:00
Item . Builder itemBuilder = Item . builder ( )
. stackSize ( customItemData . stackSize ( ) )
. maxDamage ( customItemData . maxDamage ( ) ) ;
2022-12-31 01:57:38 +00:00
Item item = new Item ( customIdentifier , itemBuilder ) {
@Override
public boolean isValidRepairItem ( Item other ) {
return repairMaterials ! = null & & repairMaterials . contains ( other . javaIdentifier ( ) ) ;
}
} ;
2022-12-29 20:10:40 +00:00
Items . register ( item , customItemData . javaId ( ) ) ;
2022-07-02 16:50:16 +00:00
ItemMapping customItemMapping = ItemMapping . builder ( )
2022-12-29 20:10:40 +00:00
. bedrockDefinition ( new SimpleItemDefinition ( customIdentifier , customItemId , true ) )
2022-07-02 16:50:16 +00:00
. bedrockData ( 0 )
2022-10-30 16:34:08 +00:00
. bedrockBlockDefinition ( null )
2022-07-02 16:50:16 +00:00
. toolType ( customItemData . toolType ( ) )
. toolTier ( customItemData . toolTier ( ) )
. translationString ( customItemData . translationString ( ) )
2022-10-10 19:40:07 +00:00
. customItemOptions ( Collections . emptyList ( ) )
2022-12-29 20:10:40 +00:00
. javaItem ( item )
2022-07-02 16:50:16 +00:00
. build ( ) ;
NbtMapBuilder builder = createComponentNbt ( customItemData , customItemData . identifier ( ) , customItemId ,
2024-03-18 18:41:36 +00:00
customItemData . isHat ( ) , customItemData . displayHandheld ( ) , protocolVersion ) ;
2022-07-02 16:50:16 +00:00
ComponentItemData componentItemData = new ComponentItemData ( customIdentifier , builder . build ( ) ) ;
2022-12-29 20:10:40 +00:00
return new NonVanillaItemRegistration ( componentItemData , item , customItemMapping ) ;
2022-07-02 16:50:16 +00:00
}
2023-05-04 00:17:05 +00:00
private static NbtMapBuilder createComponentNbt ( CustomItemData customItemData , Item javaItem , GeyserMappingItem mapping ,
2024-02-06 19:11:17 +00:00
String customItemName , int customItemId , int protocolVersion ) {
2022-07-02 16:50:16 +00:00
NbtMapBuilder builder = NbtMap . builder ( ) ;
builder . putString ( " name " , customItemName )
. putInt ( " id " , customItemId ) ;
NbtMapBuilder itemProperties = NbtMap . builder ( ) ;
NbtMapBuilder componentBuilder = NbtMap . builder ( ) ;
2024-02-06 19:11:17 +00:00
setupBasicItemInfo ( javaItem . maxDamage ( ) , javaItem . maxStackSize ( ) , mapping . getToolType ( ) ! = null | | customItemData . displayHandheld ( ) , customItemData , itemProperties , componentBuilder , protocolVersion ) ;
2022-07-02 16:50:16 +00:00
boolean canDestroyInCreative = true ;
if ( mapping . getToolType ( ) ! = null ) { // This is not using the isTool boolean because it is not just a render type here.
2024-03-18 18:41:36 +00:00
canDestroyInCreative = computeToolProperties ( mapping . getToolType ( ) , itemProperties , componentBuilder , javaItem . attackDamage ( ) ) ;
2022-07-02 16:50:16 +00:00
}
itemProperties . putBoolean ( " can_destroy_in_creative " , canDestroyInCreative ) ;
if ( mapping . getArmorType ( ) ! = null ) {
2024-02-07 17:17:51 +00:00
computeArmorProperties ( mapping . getArmorType ( ) , mapping . getProtectionValue ( ) , itemProperties , componentBuilder ) ;
2022-07-02 16:50:16 +00:00
}
2022-11-18 19:04:22 +00:00
if ( mapping . getFirstBlockRuntimeId ( ) ! = null ) {
computeBlockItemProperties ( mapping . getBedrockIdentifier ( ) , componentBuilder ) ;
}
2022-12-11 18:15:49 +00:00
if ( mapping . isEdible ( ) ) {
computeConsumableProperties ( itemProperties , componentBuilder , 1 , false ) ;
}
if ( mapping . isEntityPlacer ( ) ) {
computeEntityPlacerProperties ( componentBuilder ) ;
}
switch ( mapping . getBedrockIdentifier ( ) ) {
2023-12-05 23:54:42 +00:00
case " minecraft:fire_charge " , " minecraft:flint_and_steel " - > computeBlockItemProperties ( " minecraft:fire " , componentBuilder ) ;
2024-02-07 17:17:51 +00:00
case " minecraft:bow " , " minecraft:crossbow " , " minecraft:trident " - > computeChargeableProperties ( itemProperties , componentBuilder , mapping . getBedrockIdentifier ( ) , protocolVersion ) ;
2023-12-05 23:54:42 +00:00
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 ) ;
2022-12-11 18:15:49 +00:00
}
2022-07-02 16:50:16 +00:00
computeRenderOffsets ( false , customItemData , componentBuilder ) ;
componentBuilder . putCompound ( " item_properties " , itemProperties . build ( ) ) ;
builder . putCompound ( " components " , componentBuilder . build ( ) ) ;
return builder ;
}
private static NbtMapBuilder createComponentNbt ( NonVanillaCustomItemData customItemData , String customItemName ,
2024-03-18 18:41:36 +00:00
int customItemId , boolean isHat , boolean displayHandheld , int protocolVersion ) {
2022-07-02 16:50:16 +00:00
NbtMapBuilder builder = NbtMap . builder ( ) ;
builder . putString ( " name " , customItemName )
. putInt ( " id " , customItemId ) ;
NbtMapBuilder itemProperties = NbtMap . builder ( ) ;
NbtMapBuilder componentBuilder = NbtMap . builder ( ) ;
2024-02-06 19:11:17 +00:00
setupBasicItemInfo ( customItemData . maxDamage ( ) , customItemData . stackSize ( ) , displayHandheld , customItemData , itemProperties , componentBuilder , protocolVersion ) ;
2022-07-02 16:50:16 +00:00
boolean canDestroyInCreative = true ;
if ( customItemData . toolType ( ) ! = null ) { // This is not using the isTool boolean because it is not just a render type here.
2024-03-18 18:41:36 +00:00
canDestroyInCreative = computeToolProperties ( Objects . requireNonNull ( customItemData . toolType ( ) ) , itemProperties , componentBuilder , customItemData . attackDamage ( ) ) ;
2022-07-02 16:50:16 +00:00
}
itemProperties . putBoolean ( " can_destroy_in_creative " , canDestroyInCreative ) ;
String armorType = customItemData . armorType ( ) ;
if ( armorType ! = null ) {
2024-02-07 17:17:51 +00:00
computeArmorProperties ( armorType , customItemData . protectionValue ( ) , itemProperties , componentBuilder ) ;
2022-07-02 16:50:16 +00:00
}
2023-05-21 12:22:15 +00:00
if ( customItemData . isEdible ( ) ) {
computeConsumableProperties ( itemProperties , componentBuilder , 1 , customItemData . canAlwaysEat ( ) ) ;
}
if ( customItemData . isChargeable ( ) ) {
2024-02-07 17:17:51 +00:00
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 ) ;
2023-05-21 12:22:15 +00:00
}
2022-07-02 16:50:16 +00:00
computeRenderOffsets ( isHat , customItemData , componentBuilder ) ;
2023-05-12 23:40:17 +00:00
if ( customItemData . isFoil ( ) ) {
itemProperties . putBoolean ( " foil " , true ) ;
}
2024-03-31 11:01:59 +00:00
String block = customItemData . block ( ) ;
if ( block ! = null ) {
computeBlockItemProperties ( block , componentBuilder ) ;
}
2022-07-02 16:50:16 +00:00
componentBuilder . putCompound ( " item_properties " , itemProperties . build ( ) ) ;
builder . putCompound ( " components " , componentBuilder . build ( ) ) ;
return builder ;
}
2024-02-06 19:11:17 +00:00
private static void setupBasicItemInfo ( int maxDamage , int stackSize , boolean displayHandheld , CustomItemData customItemData , NbtMapBuilder itemProperties , NbtMapBuilder componentBuilder , int protocolVersion ) {
NbtMap iconMap ;
if ( GameProtocol . is1_20_60orHigher ( protocolVersion ) ) {
iconMap = NbtMap . builder ( )
. putCompound ( " textures " , NbtMap . builder ( )
. putString ( " default " , customItemData . icon ( ) )
. build ( ) )
. build ( ) ;
} else {
iconMap = NbtMap . builder ( )
. putString ( " texture " , customItemData . icon ( ) )
. build ( ) ;
}
itemProperties . putCompound ( " minecraft:icon " , iconMap ) ;
2024-03-10 22:38:38 +00:00
if ( customItemData . creativeCategory ( ) . isPresent ( ) ) {
itemProperties . putInt ( " creative_category " , customItemData . creativeCategory ( ) . getAsInt ( ) ) ;
if ( customItemData . creativeGroup ( ) ! = null ) {
itemProperties . putString ( " creative_group " , customItemData . creativeGroup ( ) ) ;
}
}
2022-07-02 16:50:16 +00:00
componentBuilder . putCompound ( " minecraft:display_name " , NbtMap . builder ( ) . putString ( " value " , customItemData . displayName ( ) ) . build ( ) ) ;
2023-11-09 07:44:13 +00:00
// 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 ) ;
}
}
2022-07-02 16:50:16 +00:00
itemProperties . putBoolean ( " allow_off_hand " , customItemData . allowOffhand ( ) ) ;
2023-04-11 18:32:31 +00:00
itemProperties . putBoolean ( " hand_equipped " , displayHandheld ) ;
2022-07-02 16:50:16 +00:00
itemProperties . putInt ( " max_stack_size " , stackSize ) ;
2022-10-18 03:36:46 +00:00
// Ignore durability if the item's predicate requires that it be unbreakable
if ( maxDamage > 0 & & customItemData . customItemOptions ( ) . unbreakable ( ) ! = TriState . TRUE ) {
2022-07-02 16:50:16 +00:00
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
* /
2024-03-18 18:41:36 +00:00
private static boolean computeToolProperties ( String toolType , NbtMapBuilder itemProperties , NbtMapBuilder componentBuilder , int attackDamage ) {
2022-07-02 16:50:16 +00:00
boolean canDestroyInCreative = true ;
float miningSpeed = 1 . 0f ;
2023-08-21 23:04:08 +00:00
// 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 ( )
) ) ;
2023-11-09 07:44:13 +00:00
componentBuilder . putCompound ( " minecraft:digger " ,
2023-08-21 23:04:08 +00:00
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 ;
2022-07-02 16:50:16 +00:00
}
itemProperties . putBoolean ( " hand_equipped " , true ) ;
itemProperties . putFloat ( " mining_speed " , miningSpeed ) ;
2024-02-07 17:17:51 +00:00
// 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 ) ;
2024-03-18 18:41:36 +00:00
// Adds a "attack damage" indicator. Purely visual!
if ( attackDamage > 0 ) {
itemProperties . putInt ( " damage " , attackDamage ) ;
}
2022-07-02 16:50:16 +00:00
return canDestroyInCreative ;
}
2024-02-07 17:17:51 +00:00
private static void computeArmorProperties ( String armorType , int protectionValue , NbtMapBuilder itemProperties , NbtMapBuilder componentBuilder ) {
2022-07-02 16:50:16 +00:00
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 ( ) ) ;
2024-03-10 22:38:38 +00:00
itemProperties . putString ( " enchantable_slot " , " armor_feet " ) ;
itemProperties . putInt ( " enchantable_value " , 15 ) ;
2022-07-02 16:50:16 +00:00
}
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 ( ) ) ;
2024-03-10 22:38:38 +00:00
itemProperties . putString ( " enchantable_slot " , " armor_torso " ) ;
itemProperties . putInt ( " enchantable_value " , 15 ) ;
2022-07-02 16:50:16 +00:00
}
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 ( ) ) ;
2024-03-10 22:38:38 +00:00
itemProperties . putString ( " enchantable_slot " , " armor_legs " ) ;
itemProperties . putInt ( " enchantable_value " , 15 ) ;
2022-07-02 16:50:16 +00:00
}
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 ( ) ) ;
2024-03-10 22:38:38 +00:00
itemProperties . putString ( " enchantable_slot " , " armor_head " ) ;
itemProperties . putInt ( " enchantable_value " , 15 ) ;
2022-07-02 16:50:16 +00:00
}
}
}
2022-11-18 19:04:22 +00:00
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 ( ) ) ;
}
2024-02-07 17:17:51 +00:00
private static void computeChargeableProperties ( NbtMapBuilder itemProperties , NbtMapBuilder componentBuilder , String mapping , int protocolVersion ) {
2022-12-11 18:15:49 +00:00
// 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 ) ;
2024-02-07 17:17:51 +00:00
// Make bows, tridents, and crossbows enchantable
itemProperties . putInt ( " enchantable_value " , 1 ) ;
if ( GameProtocol . is1_20_60orHigher ( protocolVersion ) ) {
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 ) ;
}
}
} else {
// ensure client moves at slow speed while charging (note: this was calculated by hand as the movement modifer value does not seem to scale linearly)
componentBuilder . putCompound ( " minecraft:chargeable " , NbtMap . builder ( ) . putFloat ( " movement_modifier " , 0 . 35F ) . build ( ) ) ;
// keep item enchantable; also works on 1.20.50
itemProperties . putString ( " enchantable_slot " , mapping . replace ( " minecraft: " , " " ) ) ;
}
2022-12-11 18:15:49 +00:00
}
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 ( ) ) ;
}
2022-07-02 16:50:16 +00:00
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 ( ) ;
}
2023-12-05 23:54:42 +00:00
private static @Nullable NbtMap toNbtMap ( CustomRenderOffsets . Hand hand ) {
2022-07-02 16:50:16 +00:00
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 ( ) ;
}
2023-12-05 23:54:42 +00:00
private static @Nullable NbtMap toNbtMap ( CustomRenderOffsets . @Nullable Offset offset ) {
2022-07-02 16:50:16 +00:00
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 ( ) ) ;
}
2023-11-09 07:44:13 +00:00
@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 ) ;
}
}
2022-07-02 16:50:16 +00:00
}
private static NbtMap xyzToScaleList ( float x , float y , float z ) {
return NbtMap . builder ( ) . putList ( " scale " , NbtType . FLOAT , List . of ( x , y , z ) ) . build ( ) ;
}
}