2021-07-13 01:19:40 +00:00
/ *
2022-01-01 19:03:05 +00:00
* Copyright ( c ) 2019 - 2022 GeyserMC . http : //geysermc.org
2021-07-13 01:19:40 +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
* /
2021-11-20 21:34:30 +00:00
package org.geysermc.geyser.registry.populator ;
2021-07-13 01:19:40 +00:00
import com.fasterxml.jackson.core.type.TypeReference ;
2022-07-02 16:50:16 +00:00
import com.google.common.collect.Multimap ;
import com.google.common.collect.MultimapBuilder ;
2022-10-30 03:02:11 +00:00
import it.unimi.dsi.fastutil.Pair ;
2022-10-30 00:23:21 +00:00
import it.unimi.dsi.fastutil.ints.Int2ObjectMap ;
import it.unimi.dsi.fastutil.ints.Int2ObjectOpenHashMap ;
import it.unimi.dsi.fastutil.ints.IntOpenHashSet ;
import it.unimi.dsi.fastutil.ints.IntSet ;
2022-12-29 20:10:40 +00:00
import it.unimi.dsi.fastutil.objects.* ;
2023-05-24 06:33:43 +00:00
import org.checkerframework.checker.nullness.qual.NonNull ;
2022-12-29 20:10:40 +00:00
import org.cloudburstmc.nbt.NbtMap ;
import org.cloudburstmc.nbt.NbtMapBuilder ;
import org.cloudburstmc.nbt.NbtType ;
2023-06-07 14:44:18 +00:00
import org.cloudburstmc.protocol.bedrock.codec.v589.Bedrock_v589 ;
2023-07-11 23:17:01 +00:00
import org.cloudburstmc.protocol.bedrock.codec.v594.Bedrock_v594 ;
2023-09-12 02:22:50 +00:00
import org.cloudburstmc.protocol.bedrock.codec.v618.Bedrock_v618 ;
2023-10-19 05:11:59 +00:00
import org.cloudburstmc.protocol.bedrock.codec.v622.Bedrock_v622 ;
2023-11-21 22:31:56 +00:00
import org.cloudburstmc.protocol.bedrock.codec.v630.Bedrock_v630 ;
2023-07-11 23:17:01 +00:00
import org.cloudburstmc.protocol.bedrock.data.SoundEvent ;
2023-05-23 20:34:50 +00:00
import org.cloudburstmc.protocol.bedrock.data.definitions.BlockDefinition ;
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 ;
import org.cloudburstmc.protocol.bedrock.data.inventory.ItemData ;
2023-09-09 17:23:19 +00:00
import org.geysermc.geyser.Constants ;
2021-12-03 16:01:06 +00:00
import org.geysermc.geyser.GeyserBootstrap ;
2021-11-20 21:34:30 +00:00
import org.geysermc.geyser.GeyserImpl ;
2023-08-21 23:04:08 +00:00
import org.geysermc.geyser.api.block.custom.CustomBlockData ;
import org.geysermc.geyser.api.block.custom.CustomBlockState ;
import org.geysermc.geyser.api.block.custom.NonVanillaCustomBlockData ;
2022-07-02 16:50:16 +00:00
import org.geysermc.geyser.api.item.custom.CustomItemData ;
import org.geysermc.geyser.api.item.custom.CustomItemOptions ;
import org.geysermc.geyser.api.item.custom.NonVanillaCustomItemData ;
2021-11-20 23:29:46 +00:00
import org.geysermc.geyser.inventory.item.StoredItemMappings ;
2022-07-02 16:50:16 +00:00
import org.geysermc.geyser.item.GeyserCustomMappingData ;
2023-08-21 23:04:08 +00:00
import org.geysermc.geyser.item.Items ;
import org.geysermc.geyser.item.type.Item ;
2023-09-09 17:23:19 +00:00
import org.geysermc.geyser.registry.BlockRegistries ;
import org.geysermc.geyser.registry.Registries ;
2021-11-20 21:34:30 +00:00
import org.geysermc.geyser.registry.type.* ;
2021-07-13 01:19:40 +00:00
import java.io.InputStream ;
import java.util.* ;
2022-10-30 16:34:08 +00:00
import java.util.concurrent.atomic.AtomicInteger ;
2021-07-13 01:19:40 +00:00
2021-07-22 02:14:00 +00:00
/ * *
* Populates the item registries .
* /
2021-07-13 01:19:40 +00:00
public class ItemRegistryPopulator {
2023-06-17 00:51:29 +00:00
record PaletteVersion ( String version , int protocolVersion , Map < Item , String > javaOnlyItems , Remapper remapper ) {
2023-05-24 06:33:43 +00:00
2023-06-17 00:51:29 +00:00
public PaletteVersion ( String version , int protocolVersion ) {
this ( version , protocolVersion , Collections . emptyMap ( ) , ( item , mapping ) - > mapping ) ;
2023-05-24 06:33:43 +00:00
}
}
@FunctionalInterface
interface Remapper {
@NonNull
GeyserMappingItem remap ( Item item , GeyserMappingItem mapping ) ;
2021-07-13 01:19:40 +00:00
}
public static void populate ( ) {
2023-09-12 02:22:50 +00:00
// Forward-map 1.20 mappings to 1.20.10
Remapper remapper594 = ( item , mapping ) - > {
2023-07-11 23:17:01 +00:00
// 1.20.10+ received parity for concrete and shulker boxes
String id = item . javaIdentifier ( ) ;
if ( id . endsWith ( " _concrete " ) | | id . endsWith ( " _shulker_box " ) ) {
// the first underscore in "_shulker_box" accounts for ignoring "minecraft:shulker_box"
// which is mapped to "minecraft:undyed_shulker_box"
return mapping . withBedrockIdentifier ( id ) ;
}
return mapping ;
2023-09-12 02:22:50 +00:00
} ;
// 1.20 to 1.20.30
Remapper remapper618 = ( item , mapping ) - > {
mapping = remapper594 . remap ( item , mapping ) ; // apply 1.20.10 remapper first
String id = item . javaIdentifier ( ) ;
if ( id . endsWith ( " concrete_powder " ) | | id . contains ( " stained_glass " ) | | ( id . endsWith ( " _terracotta " ) & & ! id . contains ( " glazed " ) ) ) {
// parity: concrete powder, stained-glass blocks and panes, and coloured terracotta
// 1. 'minecraft:terracotta' is still 'minecraft:hardened_clay'
// 2. there were no changes for glazed, but it doesn't have full parity, so ignore it.
return mapping . withBedrockIdentifier ( id ) ;
}
return mapping ;
} ;
2023-11-21 22:31:56 +00:00
Remapper remapper630 = ( item , mapping ) - > {
mapping = remapper618 . remap ( item , mapping ) ; // apply 1.20.30 remapper first
String id = item . javaIdentifier ( ) ;
// 1.20.50 replaced stone & planks to individual stone types
// E.g.: granite, diorite, andesite, polished variants, dark_oak_planks etc
if ( mapping . getBedrockIdentifier ( ) . equals ( " minecraft:stone " ) | | mapping . getBedrockIdentifier ( ) . equals ( " minecraft:planks " ) ) {
return mapping . withBedrockIdentifier ( id ) ;
}
return mapping ;
} ;
2023-09-12 02:22:50 +00:00
List < PaletteVersion > paletteVersions = new ArrayList < > ( 3 ) ;
paletteVersions . add ( new PaletteVersion ( " 1_20_0 " , Bedrock_v589 . CODEC . getProtocolVersion ( ) ) ) ;
paletteVersions . add ( new PaletteVersion ( " 1_20_10 " , Bedrock_v594 . CODEC . getProtocolVersion ( ) , Collections . emptyMap ( ) , remapper594 ) ) ;
paletteVersions . add ( new PaletteVersion ( " 1_20_30 " , Bedrock_v618 . CODEC . getProtocolVersion ( ) , Collections . emptyMap ( ) , remapper618 ) ) ;
2023-10-19 05:11:59 +00:00
paletteVersions . add ( new PaletteVersion ( " 1_20_40 " , Bedrock_v622 . CODEC . getProtocolVersion ( ) , Collections . emptyMap ( ) , remapper618 ) ) ; // NO item changes between 1.20.30 and 1.20.40
2023-11-21 22:31:56 +00:00
paletteVersions . add ( new PaletteVersion ( " 1_20_50 " , Bedrock_v630 . CODEC . getProtocolVersion ( ) , Collections . emptyMap ( ) , remapper630 ) ) ;
2022-04-17 23:53:06 +00:00
2021-12-03 16:01:06 +00:00
GeyserBootstrap bootstrap = GeyserImpl . getInstance ( ) . getBootstrap ( ) ;
2021-07-13 01:19:40 +00:00
2021-09-19 19:20:42 +00:00
TypeReference < Map < String , GeyserMappingItem > > mappingItemsType = new TypeReference < > ( ) { } ;
2021-07-13 01:19:40 +00:00
Map < String , GeyserMappingItem > items ;
2021-12-03 16:01:06 +00:00
try ( InputStream stream = bootstrap . getResource ( " mappings/items.json " ) ) {
// Load item mappings from Java Edition to Bedrock Edition
2021-11-20 21:34:30 +00:00
items = GeyserImpl . JSON_MAPPER . readValue ( stream , mappingItemsType ) ;
2021-07-13 01:19:40 +00:00
} catch ( Exception e ) {
throw new AssertionError ( " Unable to load Java runtime item IDs " , e ) ;
}
2022-07-02 16:50:16 +00:00
boolean customItemsAllowed = GeyserImpl . getInstance ( ) . getConfig ( ) . isAddNonBedrockItems ( ) ;
2022-10-10 19:40:07 +00:00
// 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 , CustomItemData > customItems = MultimapBuilder . hashKeys ( ) . arrayListValues ( ) . build ( ) ;
2022-10-30 16:34:08 +00:00
List < NonVanillaCustomItemData > nonVanillaCustomItems = customItemsAllowed ? new ObjectArrayList < > ( ) : Collections . emptyList ( ) ;
2022-07-02 16:50:16 +00:00
if ( customItemsAllowed ) {
2022-10-30 16:34:08 +00:00
CustomItemRegistryPopulator . populate ( items , customItems , nonVanillaCustomItems ) ;
2022-07-02 16:50:16 +00:00
}
2022-02-25 03:49:10 +00:00
// We can reduce some operations as Java information is the same across all palette versions
boolean firstMappingsPass = true ;
2021-07-13 01:19:40 +00:00
/* Load item palette */
2023-06-17 00:51:29 +00:00
for ( PaletteVersion palette : paletteVersions ) {
2021-09-22 19:11:14 +00:00
TypeReference < List < PaletteItem > > paletteEntriesType = new TypeReference < > ( ) { } ;
2021-07-13 01:19:40 +00:00
List < PaletteItem > itemEntries ;
2023-06-17 00:51:29 +00:00
try ( InputStream stream = bootstrap . getResource ( String . format ( " bedrock/runtime_item_states.%s.json " , palette . version ( ) ) ) ) {
2021-11-20 21:34:30 +00:00
itemEntries = GeyserImpl . JSON_MAPPER . readValue ( stream , paletteEntriesType ) ;
2021-07-13 01:19:40 +00:00
} catch ( Exception e ) {
throw new AssertionError ( " Unable to load Bedrock runtime item IDs " , e ) ;
}
2022-07-02 16:50:16 +00:00
// Used for custom items
int nextFreeBedrockId = 0 ;
List < ComponentItemData > componentItemData = new ObjectArrayList < > ( ) ;
2022-12-29 20:10:40 +00:00
Int2ObjectMap < ItemDefinition > registry = new Int2ObjectOpenHashMap < > ( ) ;
2022-10-30 03:02:11 +00:00
Map < String , ItemDefinition > definitions = new Object2ObjectLinkedOpenHashMap < > ( ) ;
2021-07-13 01:19:40 +00:00
for ( PaletteItem entry : itemEntries ) {
2022-07-02 16:50:16 +00:00
int id = entry . getId ( ) ;
if ( id > = nextFreeBedrockId ) {
nextFreeBedrockId = id + 1 ;
}
2022-12-29 20:10:40 +00:00
ItemDefinition definition = new SimpleItemDefinition ( entry . getName ( ) . intern ( ) , id , false ) ;
2022-10-30 00:23:21 +00:00
definitions . put ( entry . getName ( ) , definition ) ;
2022-12-29 20:10:40 +00:00
registry . put ( definition . getRuntimeId ( ) , definition ) ;
2021-07-13 01:19:40 +00:00
}
2022-12-23 21:18:48 +00:00
Object2ObjectMap < String , BlockDefinition > bedrockBlockIdOverrides = new Object2ObjectOpenHashMap < > ( ) ;
2021-07-13 01:19:40 +00:00
Object2IntMap < String > blacklistedIdentifiers = new Object2IntOpenHashMap < > ( ) ;
2023-08-21 23:04:08 +00:00
Object2ObjectMap < CustomBlockData , ItemDefinition > customBlockItemDefinitions = new Object2ObjectOpenHashMap < > ( ) ;
2022-10-30 00:23:21 +00:00
List < ItemDefinition > buckets = new ObjectArrayList < > ( ) ;
2021-07-13 01:19:40 +00:00
List < ItemData > carpets = new ObjectArrayList < > ( ) ;
2022-04-04 18:08:35 +00:00
List < ItemMapping > mappings = new ObjectArrayList < > ( ) ;
2021-07-13 01:19:40 +00:00
// Temporary mapping to create stored items
2022-12-29 20:10:40 +00:00
Map < Item , ItemMapping > javaItemToMapping = new Object2ObjectOpenHashMap < > ( ) ;
2021-07-13 01:19:40 +00:00
List < ItemData > creativeItems = new ArrayList < > ( ) ;
2022-10-30 16:34:08 +00:00
AtomicInteger creativeNetId = new AtomicInteger ( ) ;
CreativeItemRegistryPopulator . populate ( palette , definitions , itemBuilder - > {
2022-12-23 21:18:48 +00:00
ItemData item = itemBuilder . netId ( creativeNetId . incrementAndGet ( ) ) . build ( ) ;
2022-10-30 16:34:08 +00:00
creativeItems . add ( item ) ;
2022-10-30 03:02:11 +00:00
2022-10-30 16:34:08 +00:00
if ( item . getBlockDefinition ( ) ! = null ) {
String identifier = item . getDefinition ( ) . getIdentifier ( ) ;
2021-07-13 01:19:40 +00:00
// Add override for item mapping, unless it already exists... then we know multiple states can exist
if ( ! blacklistedIdentifiers . containsKey ( identifier ) ) {
if ( bedrockBlockIdOverrides . containsKey ( identifier ) ) {
2022-12-23 21:18:48 +00:00
bedrockBlockIdOverrides . remove ( identifier ) ;
2021-07-13 01:19:40 +00:00
// Save this as a blacklist, but also as knowledge of what the block state name should be
2022-10-30 16:34:08 +00:00
blacklistedIdentifiers . put ( identifier , item . getBlockDefinition ( ) . getRuntimeId ( ) ) ;
2021-07-13 01:19:40 +00:00
} else {
// Unless there's multiple possibilities for this one state, let this be
2022-12-23 21:18:48 +00:00
bedrockBlockIdOverrides . put ( identifier , item . getBlockDefinition ( ) ) ;
2021-07-13 01:19:40 +00:00
}
}
}
2022-10-30 16:34:08 +00:00
} ) ;
2021-07-13 01:19:40 +00:00
2023-06-17 00:51:29 +00:00
BlockMappings blockMappings = BlockRegistries . BLOCKS . forVersion ( palette . protocolVersion ( ) ) ;
2021-07-13 01:19:40 +00:00
2022-12-29 20:10:40 +00:00
Set < Item > javaOnlyItems = new ObjectOpenHashSet < > ( ) ;
Collections . addAll ( javaOnlyItems , Items . SPECTRAL_ARROW , Items . DEBUG_STICK ,
Items . KNOWLEDGE_BOOK , Items . TIPPED_ARROW , Items . BUNDLE ) ;
2022-07-02 16:50:16 +00:00
if ( ! customItemsAllowed ) {
2022-12-29 20:10:40 +00:00
javaOnlyItems . add ( Items . FURNACE_MINECART ) ;
2021-07-13 01:19:40 +00:00
}
// Java-only items for this version
2023-06-17 00:51:29 +00:00
javaOnlyItems . addAll ( palette . javaOnlyItems ( ) . keySet ( ) ) ;
2021-07-13 01:19:40 +00:00
2022-07-02 16:50:16 +00:00
Int2ObjectMap < String > customIdMappings = new Int2ObjectOpenHashMap < > ( ) ;
Set < String > registeredItemNames = new ObjectOpenHashSet < > ( ) ; // This is used to check for duplicate item names
2021-07-13 01:19:40 +00:00
for ( Map . Entry < String , GeyserMappingItem > entry : items . entrySet ( ) ) {
2022-12-29 20:10:40 +00:00
Item javaItem = Registries . JAVA_ITEM_IDENTIFIERS . get ( entry . getKey ( ) ) ;
if ( javaItem = = null ) {
throw new RuntimeException ( " Extra item in mappings? " + entry . getKey ( ) ) ;
}
2021-07-13 01:19:40 +00:00
GeyserMappingItem mappingItem ;
2023-06-17 00:51:29 +00:00
String replacementItem = palette . javaOnlyItems ( ) . get ( javaItem ) ;
2021-07-13 01:19:40 +00:00
if ( replacementItem ! = null ) {
2023-05-24 06:33:43 +00:00
mappingItem = items . get ( replacementItem ) ; // java only item, a java id fallback has been provided
2021-07-13 01:19:40 +00:00
} else {
2023-05-24 06:33:43 +00:00
// check if any mapping changes need to be made on this version
2023-06-17 00:51:29 +00:00
mappingItem = palette . remapper ( ) . remap ( javaItem , entry . getValue ( ) ) ;
2021-07-13 01:19:40 +00:00
}
2022-02-19 17:07:30 +00:00
2022-12-29 20:10:40 +00:00
if ( customItemsAllowed & & javaItem = = Items . FURNACE_MINECART ) {
2022-04-04 18:08:35 +00:00
// Will be added later
mappings . add ( null ) ;
2021-07-13 01:19:40 +00:00
continue ;
}
2022-02-19 17:07:30 +00:00
2023-05-13 04:05:22 +00:00
String bedrockIdentifier = mappingItem . getBedrockIdentifier ( ) ;
2022-10-30 03:02:11 +00:00
ItemDefinition definition = definitions . get ( bedrockIdentifier ) ;
2022-10-30 00:23:21 +00:00
if ( definition = = null ) {
2023-06-17 00:51:29 +00:00
throw new RuntimeException ( " Missing Bedrock ItemDefinition in version " + palette . version ( ) + " for mapping: " + mappingItem ) ;
2021-07-13 01:19:40 +00:00
}
2022-10-30 00:23:21 +00:00
2022-12-23 21:18:48 +00:00
BlockDefinition bedrockBlock = null ;
2021-07-17 22:09:55 +00:00
Integer firstBlockRuntimeId = entry . getValue ( ) . getFirstBlockRuntimeId ( ) ;
2023-08-21 23:04:08 +00:00
BlockDefinition customBlockItemOverride = null ;
2021-07-17 22:09:55 +00:00
if ( firstBlockRuntimeId ! = null ) {
2022-12-23 21:18:48 +00:00
BlockDefinition blockOverride = bedrockBlockIdOverrides . get ( bedrockIdentifier ) ;
2023-08-21 23:04:08 +00:00
// We'll do this here for custom blocks we want in the creative inventory so we can piggyback off the existing logic to find these
// blocks in creativeItems
CustomBlockData customBlockData = BlockRegistries . CUSTOM_BLOCK_ITEM_OVERRIDES . getOrDefault ( javaItem . javaIdentifier ( ) , null ) ;
if ( customBlockData ! = null ) {
// this block has a custom item override and thus we should use its runtime ID for the ItemMapping
if ( customBlockData . includedInCreativeInventory ( ) ) {
CustomBlockState customBlockState = customBlockData . defaultBlockState ( ) ;
customBlockItemOverride = blockMappings . getCustomBlockStateDefinitions ( ) . getOrDefault ( customBlockState , null ) ;
}
}
// If it' s a custom block we can't do this because we need to make sure we find the creative item
if ( blockOverride ! = null & & customBlockItemOverride = = null ) {
2021-07-13 01:19:40 +00:00
// Straight from BDS is our best chance of getting an item that doesn't run into issues
2022-12-23 21:18:48 +00:00
bedrockBlock = blockOverride ;
2021-07-13 01:19:40 +00:00
} else {
// Try to get an example block runtime ID from the creative contents packet, for Bedrock identifier obtaining
2023-08-21 23:04:08 +00:00
int aValidBedrockBlockId = blacklistedIdentifiers . getOrDefault ( bedrockIdentifier , customBlockItemOverride ! = null ? customBlockItemOverride . getRuntimeId ( ) : - 1 ) ;
if ( aValidBedrockBlockId = = - 1 & & customBlockItemOverride = = null ) {
2021-07-13 01:19:40 +00:00
// Fallback
2022-12-23 21:18:48 +00:00
bedrockBlock = blockMappings . getBedrockBlock ( firstBlockRuntimeId ) ;
2021-07-13 01:19:40 +00:00
} else {
// As of 1.16.220, every item requires a block runtime ID attached to it.
// This is mostly for identifying different blocks with the same item ID - wool, slabs, some walls.
// However, in order for some visuals and crafting to work, we need to send the first matching block state
// as indexed by Bedrock's block palette
// There are exceptions! But, ideally, the block ID override should take care of those.
NbtMapBuilder requiredBlockStatesBuilder = NbtMap . builder ( ) ;
2022-12-24 00:40:42 +00:00
String correctBedrockIdentifier = blockMappings . getDefinition ( aValidBedrockBlockId ) . getState ( ) . getString ( " name " ) ;
2021-07-13 01:19:40 +00:00
boolean firstPass = true ;
2021-07-17 22:09:55 +00:00
// Block states are all grouped together. In the mappings, we store the first block runtime ID in order,
// and the last, if relevant. We then iterate over all those values and get their Bedrock equivalents
Integer lastBlockRuntimeId = entry . getValue ( ) . getLastBlockRuntimeId ( ) = = null ? firstBlockRuntimeId : entry . getValue ( ) . getLastBlockRuntimeId ( ) ;
for ( int i = firstBlockRuntimeId ; i < = lastBlockRuntimeId ; i + + ) {
2023-08-21 23:04:08 +00:00
GeyserBedrockBlock bedrockBlockRuntimeId = blockMappings . getVanillaBedrockBlock ( i ) ;
2022-12-23 21:18:48 +00:00
NbtMap blockTag = bedrockBlockRuntimeId . getState ( ) ;
2021-07-17 22:09:55 +00:00
String bedrockName = blockTag . getString ( " name " ) ;
if ( ! bedrockName . equals ( correctBedrockIdentifier ) ) {
continue ;
}
NbtMap states = blockTag . getCompound ( " states " ) ;
if ( firstPass ) {
firstPass = false ;
if ( states . size ( ) = = 0 ) {
// No need to iterate and find all block states - this is the one, as there can't be any others
2022-12-23 21:18:48 +00:00
bedrockBlock = bedrockBlockRuntimeId ;
2021-07-13 01:19:40 +00:00
break ;
}
2021-07-17 22:09:55 +00:00
requiredBlockStatesBuilder . putAll ( states ) ;
continue ;
}
for ( Map . Entry < String , Object > nbtEntry : states . entrySet ( ) ) {
Object value = requiredBlockStatesBuilder . get ( nbtEntry . getKey ( ) ) ;
if ( value ! = null & & ! nbtEntry . getValue ( ) . equals ( value ) ) { // Null means this value has already been removed/deemed as unneeded
// This state can change between different block states, and therefore is not required
// to build a successful block state of this
requiredBlockStatesBuilder . remove ( nbtEntry . getKey ( ) ) ;
}
}
if ( requiredBlockStatesBuilder . size ( ) = = 0 ) {
// There are no required block states
// E.G. there was only a direction property that is no longer in play
// (States that are important include color for glass)
break ;
2021-07-13 01:19:40 +00:00
}
}
NbtMap requiredBlockStates = requiredBlockStatesBuilder . build ( ) ;
2022-12-23 21:18:48 +00:00
if ( bedrockBlock = = null ) {
2021-07-13 01:19:40 +00:00
// We need to loop around again (we can't cache the block tags above) because Bedrock can include states that we don't have a pairing for
// in it's "preferred" block state - I.E. the first matching block state in the list
2022-12-24 00:40:42 +00:00
for ( GeyserBedrockBlock block : blockMappings . getBedrockRuntimeMap ( ) ) {
if ( block = = null ) {
continue ;
}
NbtMap blockTag = block . getState ( ) ;
2021-07-13 01:19:40 +00:00
if ( blockTag . getString ( " name " ) . equals ( correctBedrockIdentifier ) ) {
NbtMap states = blockTag . getCompound ( " states " ) ;
boolean valid = true ;
for ( Map . Entry < String , Object > nbtEntry : requiredBlockStates . entrySet ( ) ) {
if ( ! states . get ( nbtEntry . getKey ( ) ) . equals ( nbtEntry . getValue ( ) ) ) {
// A required block state doesn't match - this one is not valid
valid = false ;
break ;
}
}
if ( valid ) {
2022-12-24 00:40:42 +00:00
bedrockBlock = block ;
2021-07-13 01:19:40 +00:00
break ;
}
}
}
2022-12-23 21:18:48 +00:00
if ( bedrockBlock = = null ) {
2021-07-13 01:19:40 +00:00
throw new RuntimeException ( " Could not find a block match for " + entry . getKey ( ) ) ;
}
}
// Because we have replaced the Bedrock block ID, we also need to replace the creative contents block runtime ID
// That way, creative items work correctly for these blocks
2023-08-21 23:04:08 +00:00
// Set our custom block override now if there is one
if ( customBlockItemOverride ! = null ) {
bedrockBlock = customBlockItemOverride ;
}
2021-07-13 01:19:40 +00:00
for ( int j = 0 ; j < creativeItems . size ( ) ; j + + ) {
ItemData itemData = creativeItems . get ( j ) ;
2022-10-30 00:23:21 +00:00
if ( itemData . getDefinition ( ) . equals ( definition ) ) {
2021-07-13 01:19:40 +00:00
if ( itemData . getDamage ( ) ! = 0 ) {
break ;
}
2022-10-30 00:23:21 +00:00
2022-12-24 00:40:42 +00:00
NbtMap states = ( ( GeyserBedrockBlock ) itemData . getBlockDefinition ( ) ) . getState ( ) . getCompound ( " states " ) ;
2023-08-21 23:04:08 +00:00
2021-07-13 01:19:40 +00:00
boolean valid = true ;
for ( Map . Entry < String , Object > nbtEntry : requiredBlockStates . entrySet ( ) ) {
2023-09-09 17:23:19 +00:00
if ( ! Objects . equals ( states . get ( nbtEntry . getKey ( ) ) , nbtEntry . getValue ( ) ) ) {
2021-07-13 01:19:40 +00:00
// A required block state doesn't match - this one is not valid
valid = false ;
break ;
}
}
if ( valid ) {
2023-08-21 23:04:08 +00:00
if ( customBlockItemOverride ! = null & & customBlockData ! = null ) {
// Assuming this is a valid custom block override we'll just register it now while we have the creative item
int customProtocolId = nextFreeBedrockId + + ;
mappingItem . setBedrockData ( customProtocolId ) ;
bedrockIdentifier = customBlockData . identifier ( ) ;
definition = new SimpleItemDefinition ( bedrockIdentifier , customProtocolId , true ) ;
registry . put ( customProtocolId , definition ) ;
customBlockItemDefinitions . put ( customBlockData , definition ) ;
customIdMappings . put ( customProtocolId , bedrockIdentifier ) ;
creativeItems . set ( j , itemData . toBuilder ( )
. definition ( definition )
. blockDefinition ( bedrockBlock )
. netId ( itemData . getNetId ( ) )
. count ( 1 )
. build ( ) ) ;
} else {
creativeItems . set ( j , itemData . toBuilder ( ) . blockDefinition ( bedrockBlock ) . build ( ) ) ;
}
2021-07-13 01:19:40 +00:00
break ;
}
}
}
}
}
}
ItemMapping . ItemMappingBuilder mappingBuilder = ItemMapping . builder ( )
2022-02-19 17:07:30 +00:00
. bedrockIdentifier ( bedrockIdentifier . intern ( ) )
2022-10-30 00:23:21 +00:00
. bedrockDefinition ( definition )
2021-07-13 01:19:40 +00:00
. bedrockData ( mappingItem . getBedrockData ( ) )
2022-12-23 21:18:48 +00:00
. bedrockBlockDefinition ( bedrockBlock )
2022-12-29 20:10:40 +00:00
. javaItem ( javaItem ) ;
2021-11-15 01:15:25 +00:00
2021-07-13 01:19:40 +00:00
if ( mappingItem . getToolType ( ) ! = null ) {
if ( mappingItem . getToolTier ( ) ! = null ) {
2021-08-12 00:16:10 +00:00
mappingBuilder = mappingBuilder . toolType ( mappingItem . getToolType ( ) . intern ( ) )
. toolTier ( mappingItem . getToolTier ( ) . intern ( ) ) ;
2021-07-13 01:19:40 +00:00
} else {
2021-08-12 00:16:10 +00:00
mappingBuilder = mappingBuilder . toolType ( mappingItem . getToolType ( ) . intern ( ) )
2021-07-13 01:19:40 +00:00
. toolTier ( " " ) ;
}
}
2022-07-02 16:50:16 +00:00
2022-12-29 20:10:40 +00:00
if ( javaOnlyItems . contains ( javaItem ) ) {
2021-07-13 01:19:40 +00:00
// These items don't exist on Bedrock, so set up a variable that indicates they should have custom names
2022-12-23 21:18:48 +00:00
mappingBuilder = mappingBuilder . translationString ( ( bedrockBlock ! = null ? " block. " : " item. " ) + entry . getKey ( ) . replace ( " : " , " . " ) ) ;
2021-11-20 21:34:30 +00:00
GeyserImpl . getInstance ( ) . getLogger ( ) . debug ( " Adding " + entry . getKey ( ) + " as an item that needs to be translated. " ) ;
2021-07-13 01:19:40 +00:00
}
2022-07-02 16:50:16 +00:00
// Add the custom item properties, if applicable
2022-10-30 03:02:11 +00:00
List < Pair < CustomItemOptions , ItemDefinition > > customItemOptions ;
2022-12-29 20:10:40 +00:00
Collection < CustomItemData > customItemsToLoad = customItems . get ( javaItem . javaIdentifier ( ) ) ;
2022-07-02 16:50:16 +00:00
if ( customItemsAllowed & & ! customItemsToLoad . isEmpty ( ) ) {
2022-10-10 19:40:07 +00:00
customItemOptions = new ObjectArrayList < > ( customItemsToLoad . size ( ) ) ;
2022-07-02 16:50:16 +00:00
for ( CustomItemData customItem : customItemsToLoad ) {
int customProtocolId = nextFreeBedrockId + + ;
2023-08-21 23:04:08 +00:00
String customItemName = customItem instanceof NonVanillaCustomItemData nonVanillaItem ? nonVanillaItem . identifier ( ) : Constants . GEYSER_CUSTOM_NAMESPACE + " : " + customItem . name ( ) ;
2022-07-02 16:50:16 +00:00
if ( ! registeredItemNames . add ( customItemName ) ) {
if ( firstMappingsPass ) {
2023-08-21 23:04:08 +00:00
GeyserImpl . getInstance ( ) . getLogger ( ) . error ( " Custom item name ' " + customItemName + " ' already exists and was registered again! Skipping... " ) ;
2022-07-02 16:50:16 +00:00
}
continue ;
}
GeyserCustomMappingData customMapping = CustomItemRegistryPopulator . registerCustomItem (
2023-05-04 00:17:05 +00:00
customItemName , javaItem , mappingItem , customItem , customProtocolId
2022-07-02 16:50:16 +00:00
) ;
// ComponentItemData - used to register some custom properties
componentItemData . add ( customMapping . componentItemData ( ) ) ;
2022-10-30 03:02:11 +00:00
customItemOptions . add ( Pair . of ( customItem . customItemOptions ( ) , customMapping . itemDefinition ( ) ) ) ;
2023-04-09 16:30:25 +00:00
registry . put ( customMapping . integerId ( ) , customMapping . itemDefinition ( ) ) ;
2022-07-02 16:50:16 +00:00
customIdMappings . put ( customMapping . integerId ( ) , customMapping . stringId ( ) ) ;
}
2022-10-10 19:40:07 +00:00
// Important for later to find the best match and accurately replicate Java behavior
Collections . reverse ( customItemOptions ) ;
2022-07-02 16:50:16 +00:00
} else {
2022-10-10 19:40:07 +00:00
customItemOptions = Collections . emptyList ( ) ;
2022-07-02 16:50:16 +00:00
}
mappingBuilder . customItemOptions ( customItemOptions ) ;
2021-07-13 01:19:40 +00:00
ItemMapping mapping = mappingBuilder . build ( ) ;
2023-04-08 16:45:13 +00:00
if ( javaItem . javaIdentifier ( ) . contains ( " bucket " ) & & ! javaItem . javaIdentifier ( ) . contains ( " milk " ) ) {
2022-10-30 00:23:21 +00:00
buckets . add ( definition ) ;
2023-04-08 16:45:13 +00:00
} else if ( javaItem . javaIdentifier ( ) . contains ( " _carpet " ) & & ! javaItem . javaIdentifier ( ) . contains ( " moss " ) ) {
2021-07-13 01:19:40 +00:00
// This should be the numerical order Java sends as an integer value for llamas
carpets . add ( ItemData . builder ( )
2022-10-30 00:23:21 +00:00
. definition ( definition )
2021-07-13 01:19:40 +00:00
. damage ( mapping . getBedrockData ( ) )
. count ( 1 )
2022-10-30 16:34:08 +00:00
. blockDefinition ( mapping . getBedrockBlockDefinition ( ) )
2021-07-13 01:19:40 +00:00
. build ( ) ) ;
2023-04-08 16:45:13 +00:00
} else if ( javaItem . javaIdentifier ( ) . startsWith ( " minecraft:music_disc_ " ) ) {
2021-07-13 01:19:40 +00:00
// The Java record level event uses the item ID as the "key" to play the record
2022-12-29 20:10:40 +00:00
Registries . RECORDS . register ( javaItem . javaId ( ) , SoundEvent . valueOf ( " RECORD_ " +
2023-05-12 00:08:43 +00:00
mapping . getBedrockIdentifier ( ) . replace ( " minecraft:music_disc_ " , " " ) . toUpperCase ( Locale . ENGLISH ) ) ) ;
2021-07-13 01:19:40 +00:00
}
2022-04-04 18:08:35 +00:00
mappings . add ( mapping ) ;
2022-12-29 20:10:40 +00:00
javaItemToMapping . put ( javaItem , mapping ) ;
2021-07-13 01:19:40 +00:00
}
2022-10-30 00:23:21 +00:00
ItemDefinition lodestoneCompass = definitions . get ( " minecraft:lodestone_compass " ) ;
if ( lodestoneCompass = = null ) {
2021-07-13 01:19:40 +00:00
throw new RuntimeException ( " Lodestone compass not found in item palette! " ) ;
}
// Add the lodestone compass since it doesn't exist on java but we need it for item conversion
ItemMapping lodestoneEntry = ItemMapping . builder ( )
2022-12-29 20:10:40 +00:00
. javaItem ( Items . COMPASS )
2021-07-13 01:19:40 +00:00
. bedrockIdentifier ( " minecraft:lodestone_compass " )
2022-10-30 00:23:21 +00:00
. bedrockDefinition ( lodestoneCompass )
2021-07-13 01:19:40 +00:00
. bedrockData ( 0 )
2022-10-30 16:34:08 +00:00
. bedrockBlockDefinition ( null )
2022-10-10 19:40:07 +00:00
. customItemOptions ( Collections . emptyList ( ) )
2021-07-13 01:19:40 +00:00
. build ( ) ;
2022-07-02 16:50:16 +00:00
if ( customItemsAllowed ) {
2022-10-30 16:34:08 +00:00
// Add furnace minecart
2022-12-29 20:10:40 +00:00
ItemDefinition definition = new SimpleItemDefinition ( " geysermc:furnace_minecart " , nextFreeBedrockId , true ) ;
2022-10-30 00:23:21 +00:00
definitions . put ( " geysermc:furnace_minecart " , definition ) ;
2022-12-29 20:10:40 +00:00
registry . put ( definition . getRuntimeId ( ) , definition ) ;
2021-07-13 01:19:40 +00:00
2022-12-29 20:10:40 +00:00
mappings . set ( Items . FURNACE_MINECART . javaId ( ) , ItemMapping . builder ( )
. javaItem ( Items . FURNACE_MINECART )
2021-07-13 01:19:40 +00:00
. bedrockIdentifier ( " geysermc:furnace_minecart " )
2022-10-30 00:23:21 +00:00
. bedrockDefinition ( definition )
2021-07-13 01:19:40 +00:00
. bedrockData ( 0 )
2022-10-30 16:34:08 +00:00
. bedrockBlockDefinition ( null )
2022-10-10 19:40:07 +00:00
. customItemOptions ( Collections . emptyList ( ) ) // TODO check for custom items with furnace minecart
2021-07-13 01:19:40 +00:00
. build ( ) ) ;
creativeItems . add ( ItemData . builder ( )
2023-05-10 08:41:36 +00:00
. netId ( creativeNetId . incrementAndGet ( ) )
2022-10-30 00:23:21 +00:00
. definition ( definition )
2022-10-30 16:34:08 +00:00
. count ( 1 )
2021-07-13 01:19:40 +00:00
. build ( ) ) ;
2022-10-30 16:34:08 +00:00
registerFurnaceMinecart ( nextFreeBedrockId + + , componentItemData ) ;
2022-07-02 16:50:16 +00:00
// Register any completely custom items given to us
IntSet registeredJavaIds = new IntOpenHashSet ( ) ; // Used to check for duplicate item java ids
for ( NonVanillaCustomItemData customItem : nonVanillaCustomItems ) {
if ( ! registeredJavaIds . add ( customItem . javaId ( ) ) ) {
if ( firstMappingsPass ) {
GeyserImpl . getInstance ( ) . getLogger ( ) . error ( " Custom item java id " + customItem . javaId ( ) + " already exists and was registered again! Skipping... " ) ;
}
continue ;
}
int customItemId = nextFreeBedrockId + + ;
NonVanillaItemRegistration registration = CustomItemRegistryPopulator . registerCustomItem ( customItem , customItemId ) ;
componentItemData . add ( registration . componentItemData ( ) ) ;
ItemMapping mapping = registration . mapping ( ) ;
2022-12-29 20:10:40 +00:00
Item javaItem = registration . javaItem ( ) ;
while ( javaItem . javaId ( ) > = mappings . size ( ) ) {
2022-07-02 16:50:16 +00:00
// Fill with empty to get to the correct size
mappings . add ( ItemMapping . AIR ) ;
}
2022-12-29 20:10:40 +00:00
mappings . set ( javaItem . javaId ( ) , mapping ) ;
2023-05-08 19:54:49 +00:00
registry . put ( customItemId , mapping . getBedrockDefinition ( ) ) ;
2022-07-02 16:50:16 +00:00
if ( customItem . creativeGroup ( ) ! = null | | customItem . creativeCategory ( ) . isPresent ( ) ) {
creativeItems . add ( ItemData . builder ( )
2022-10-30 00:23:21 +00:00
. definition ( registration . mapping ( ) . getBedrockDefinition ( ) )
2023-05-10 08:41:36 +00:00
. netId ( creativeNetId . incrementAndGet ( ) )
2022-10-30 16:34:08 +00:00
. count ( 1 )
. build ( ) ) ;
2022-07-02 16:50:16 +00:00
}
}
2021-07-13 01:19:40 +00:00
}
2023-08-21 23:04:08 +00:00
// Register the item forms of custom blocks
if ( BlockRegistries . CUSTOM_BLOCKS . get ( ) . length ! = 0 ) {
for ( CustomBlockData customBlock : BlockRegistries . CUSTOM_BLOCKS . get ( ) ) {
// We might've registered it already with the vanilla blocks so check first
if ( customBlockItemDefinitions . containsKey ( customBlock ) ) {
continue ;
}
// Non-vanilla custom blocks will be handled in the item
// registry, so we don't need to do anything here.
if ( customBlock instanceof NonVanillaCustomBlockData ) {
continue ;
}
int customProtocolId = nextFreeBedrockId + + ;
String identifier = customBlock . identifier ( ) ;
final ItemDefinition definition = new SimpleItemDefinition ( identifier , customProtocolId , true ) ;
registry . put ( customProtocolId , definition ) ;
customBlockItemDefinitions . put ( customBlock , definition ) ;
customIdMappings . put ( customProtocolId , identifier ) ;
GeyserBedrockBlock bedrockBlock = blockMappings . getCustomBlockStateDefinitions ( ) . getOrDefault ( customBlock . defaultBlockState ( ) , null ) ;
if ( bedrockBlock ! = null & & customBlock . includedInCreativeInventory ( ) ) {
creativeItems . add ( ItemData . builder ( )
. definition ( definition )
. blockDefinition ( bedrockBlock )
. netId ( creativeNetId . incrementAndGet ( ) )
. count ( 1 )
. build ( ) ) ;
}
}
}
2021-07-13 01:19:40 +00:00
ItemMappings itemMappings = ItemMappings . builder ( )
2022-04-04 18:08:35 +00:00
. items ( mappings . toArray ( new ItemMapping [ 0 ] ) )
2021-07-13 01:19:40 +00:00
. creativeItems ( creativeItems . toArray ( new ItemData [ 0 ] ) )
2022-12-29 20:10:40 +00:00
. itemDefinitions ( registry )
. storedItems ( new StoredItemMappings ( javaItemToMapping ) )
2021-07-13 01:19:40 +00:00
. javaOnlyItems ( javaOnlyItems )
2022-10-30 00:23:21 +00:00
. buckets ( buckets )
2021-07-13 01:19:40 +00:00
. carpets ( carpets )
2022-07-02 16:50:16 +00:00
. componentItemData ( componentItemData )
2022-04-04 18:08:35 +00:00
. lodestoneCompass ( lodestoneEntry )
2022-07-02 16:50:16 +00:00
. customIdMappings ( customIdMappings )
2023-08-21 23:04:08 +00:00
. customBlockItemDefinitions ( customBlockItemDefinitions )
2021-07-13 01:19:40 +00:00
. build ( ) ;
2023-06-17 00:51:29 +00:00
Registries . ITEMS . register ( palette . protocolVersion ( ) , itemMappings ) ;
2022-02-25 03:49:10 +00:00
firstMappingsPass = false ;
2021-07-13 01:19:40 +00:00
}
}
2022-10-30 16:34:08 +00:00
private static void registerFurnaceMinecart ( int nextFreeBedrockId , List < ComponentItemData > componentItemData ) {
NbtMapBuilder builder = NbtMap . builder ( ) ;
builder . putString ( " name " , " geysermc:furnace_minecart " )
. putInt ( " id " , nextFreeBedrockId ) ;
NbtMapBuilder itemProperties = NbtMap . builder ( ) ;
NbtMapBuilder componentBuilder = NbtMap . builder ( ) ;
// Conveniently, as of 1.16.200, the furnace minecart has a texture AND translation string already.
itemProperties . putCompound ( " minecraft:icon " , NbtMap . builder ( )
. putString ( " texture " , " minecart_furnace " )
. putString ( " frame " , " 0.000000 " )
. putInt ( " frame_version " , 1 )
. putString ( " legacy_id " , " " ) . build ( ) ) ;
componentBuilder . putCompound ( " minecraft:display_name " , NbtMap . builder ( ) . putString ( " value " , " item.minecartFurnace.name " ) . build ( ) ) ;
// Indicate that the arm animation should play on rails
List < NbtMap > useOnTag = Collections . singletonList ( NbtMap . builder ( ) . putString ( " tags " , " q.any_tag('rail') " ) . build ( ) ) ;
componentBuilder . putCompound ( " minecraft:entity_placer " , NbtMap . builder ( )
. putList ( " dispense_on " , NbtType . COMPOUND , useOnTag )
. putString ( " entity " , " minecraft:minecart " )
. putList ( " use_on " , NbtType . COMPOUND , useOnTag )
. build ( ) ) ;
// We always want to allow offhand usage when we can - matches Java Edition
itemProperties . putBoolean ( " allow_off_hand " , true ) ;
itemProperties . putBoolean ( " hand_equipped " , false ) ;
itemProperties . putInt ( " max_stack_size " , 1 ) ;
itemProperties . putString ( " creative_group " , " itemGroup.name.minecart " ) ;
itemProperties . putInt ( " creative_category " , 4 ) ; // 4 - "Items"
componentBuilder . putCompound ( " item_properties " , itemProperties . build ( ) ) ;
builder . putCompound ( " components " , componentBuilder . build ( ) ) ;
componentItemData . add ( new ComponentItemData ( " geysermc:furnace_minecart " , builder . build ( ) ) ) ;
}
2021-07-13 01:19:40 +00:00
}